通用 keyof 和 属性 getter 函数
Generic keyof and property getter function
我正在尝试观察数组中对象的属性。为了使其类型安全,我使用 getter 函数获取包含要观察的实际 属性 的数组对象的子对象(如有必要)。
propertyName / key
必须是一个字符串,因为我正在使用的观察库需要它。
getter 应该能够接受 returns 与传入的类型相同的函数,例如 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
的类型,有没有我可以使用的解决方法?
关于编译器为何不推断 B
,我没有明确的答案。我的直觉是,编译器推断您实际传递给函数的参数类型比编译器仅推断类型 related 到您传递的参数要容易得多in. 基于这种直觉,我将重写类型以将 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
参数在 [=31] 之前被检查=] 参数。 (我不认为 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>(...)
哪个好,对吧?
我们需要支持的任何其他用例吗?
与@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
就语法而言,它有点丑陋,但您可以获得完整的类型安全。
我正在尝试观察数组中对象的属性。为了使其类型安全,我使用 getter 函数获取包含要观察的实际 属性 的数组对象的子对象(如有必要)。
propertyName / key
必须是一个字符串,因为我正在使用的观察库需要它。
getter 应该能够接受 returns 与传入的类型相同的函数,例如 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
的类型,有没有我可以使用的解决方法?
关于编译器为何不推断 B
,我没有明确的答案。我的直觉是,编译器推断您实际传递给函数的参数类型比编译器仅推断类型 related 到您传递的参数要容易得多in. 基于这种直觉,我将重写类型以将 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
参数在 [=31] 之前被检查=] 参数。 (我不认为 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>(...)
哪个好,对吧?
我们需要支持的任何其他用例吗?
与@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
就语法而言,它有点丑陋,但您可以获得完整的类型安全。