1

我正在尝试观察数组中对象的属性。为了使其类型安全,我使用 getter 函数来获取包含要观察的实际属性的数组对象(如有必要)的子对象。

propertyName / key必须是一个字符串,因为我正在使用的观察库需要它。

getter 应该能够接受返回与传入相同类型的函数,例如o => o.

您可以在下面找到一个精简的示例:

function foo<A, B>(
    array: A[], 
    getter: (ao: A) => B, 
    key: keyof B, 
    callback: (value: B[keyof B]) => void
) {
    callback(getter(array[0])[key]);
}

foo([{b: {c: 1}}], a => a.b, "c", v => {});

唉,这抛出Argument of type '"c"' is not assignable to parameter of type 'never'.

但以下确实有效:

function bar<A>(
    array: A[], 
    key: keyof A, 
    callback: (value: A[keyof A]) => void
) {
    callback(array[0][key]);
}

bar([{b: 1}], "b", v => {});

为什么编译器无法推断出的类型,B是否有我可以使用的解决方法?

4

2 回答 2

2

对于编译器为什么不推断,我没有明确的答案B。我的直觉是,编译器推断您实际传递给函数的参数的类型要比推断仅与您传入的参数相关的类型要容易得多。基于这种直觉,我会重写要替换的类型BRecord<K,V>其中K是您实际传入的键,并且V是与该键关联的属性的值的类型。

如果您不知道,Record<K,V>它是 TypeScript 标准库的一部分,并且定义如下:

type Record<K extends string, T> = {
    [P in K]: T; 
}

那么函数就变成了:

function foo<A, K extends string, V>(
    array: A[], 
    getter: (ao: A) => Record<K,V>, 
    key: K, // easier to infer K because you directly pass it in
    callback: (value: V) => void
) {
    callback(getter(array[0])[key]);
}

这似乎有效:

foo([{ b: { c: 1 } }], a => a.b, "c", v => { });

根据需要推断参数<{ b: { c: number; }; }, "c", number>

希望有帮助;祝你好运!


更新

@TitianCernicovaDragomir 指出以下调用:

foo([{ b: { c: 1, d: "" } }], a => a.b, "c", v => { });

被推断为

foo<{ b: { c: number; d: string; }; }, "c" | "d", string | number>(...)

这里的问题是你想要K只是"c",而不是"c" | "d",但显然array参数是在参数之前检查的key。(我不认为这Vstring | number实际的问题,因为callback实际上它会取任何值。如果你传递了一个callback只取的函数number并且它失败了,那么这将是一个问题。)

如果您发现某些类型参数的推断顺序错误(意味着编译器使用参数来推断某些内容,但您希望它使用不同的参数),您可以通过将类型与{}.

在这种情况下,我们可以这样做:

function foo<A, K extends string, V>(
  array: A[],
  getter: (ao: A) => Record<K & {}, V>, // delay inference of K
  key: K, 
  callback: (value: V) => void
) {
  callback(getter(array[0])[key]);
}

现在当我们打电话

foo([{ b: { c: 1, d: "" } }], a => a.b, "c", v => { });

推断为

foo<{ b: { c: number; d: string; }; }, "c", {}>(...)

这工作正常,因为回调不做任何事情。如果我们打电话

foo([{ b: { c: 1, d: "" } }], a => a.b, "c", (v: number): void => { });

然后推断为

foo<{ b: { c: number; d: string; }; }, "c", number>(...)

这很好,对吧?

我们需要支持的任何其他用例?

于 2017-11-20T19:41:52.927 回答
1

与@jcalz 类似,我不一定有原因,只是一种解决方法。如果您以两次调用的方式执行此操作,则可以在第一次调用中修复Aand B,并在第二次调用中发送 thekey和 the callback,然后将正确推断类型。

function foo2<A, B>(
    array: A[],
    getter: (ao: A) => B,
) {
    return function <K extends keyof B>(key: K, callback: (value: B[K]) => void) {
        callback(getter(array[0])[key]);
    }
}

foo2([{ b: { c: 1, d: "" } }], a => a.b)("d", v => { }); // v is string
foo2([{ b: { c: 1, d: "" } }], a => a.b)("c", v => { }); // v is number

就语法而言,它有点难看,但您可以获得完整的类型安全性。

于 2017-11-21T09:41:28.773 回答