我正在尝试观察数组中对象的属性。为了使其安全,我使用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
的类型,是否有可以使用的解决方法?
答案 0 :(得分:2)
关于为什么编译器不推断B
,我没有明确的答案。我的直觉是,编译器更容易推断实际传递给函数的参数的类型,而不是仅仅推断与传入的参数的相关的类型。在这种直觉上,我将用B
重写要替换Record<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
之前已经过检查参数。 (我不认为V
是string | number
的事实是实际问题,因为callback
实际上取任何值。如果你传递的callback
函数只是花了number
然后失败了,那就是个问题。)
如果您发现某些类型参数以错误的顺序推断(意味着编译器使用参数来推断某些内容,但您希望它使用不同的参数),您可以通过相交来推断lower the priority输入{}
。
在这种情况下,我们可以这样做:
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>(...)
哪个好,对吧?
我们需要支持哪些其他用例?
答案 1 :(得分:1)
与@jcalz类似,我不一定有理由,只是一种解决方法。如果您采用双呼叫方式,则可以在第一次通话中修复A
和B
,并在第二次通话中发送key
和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
就语法而言,它有点丑陋,但你可以获得完整的类型安全性。