Typescript 泛型keyof和属性getter函数

Typescript 泛型keyof和属性getter函数,typescript,generics,keyof,Typescript,Generics,Keyof,我试图观察数组中对象的属性。为了使其类型安全,我使用getter函数来获取数组对象的子对象(如果需要),该数组对象包含要观察的实际属性 propertyName/key必须是一个字符串,因为我使用的观察库需要它 getter应该能够接受返回与传入的类型相同的函数,比如o=>o 您可以在下面找到一个简明示例: function foo<A, B>( array: A[], getter: (ao: A) => B, key: keyof B,

我试图观察数组中对象的属性。为了使其类型安全,我使用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 => {});

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

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

如果您不知道,
记录
是TypeScript标准库的一部分,定义如下:

type Record<K extends string, T> = {
    [P in K]: T; 
}
参数根据需要推断为

希望有帮助;祝你好运


更新 @TitianCernicovaDragomir指出以下呼吁:

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

foo<{ b: { c: number; d: string; }; }, "c" | "d", string | number>(...)
现在当我们打电话的时候

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

foo<{ b: { c: number; d: string; }; }, "c", {}>(...)
foo<{ b: { c: number; d: string; }; }, "c", number>(...)
那么它被推断为

foo<{ b: { c: number; d: string; }; }, "c", {}>(...)
foo<{ b: { c: number; d: string; }; }, "c", number>(...)
foo(…)
哪个好,对吗


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

类似于@jcalz,我不一定有原因,只是一个解决方法。如果采用两次调用的方法,可以在第一次调用中修复
a
B
,并在第二次调用中发送
键和
回调
,然后正确推断类型

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
函数foo2(
数组:A[],
getter:(ao:A)=>B,
) {
返回函数(键:K,回调:(值:B[K])=>void){
回调(getter(数组[0])[key]);
}
}
foo2([{b:{c:1,d:'}}],a=>a.b)(“d”,v=>{});//v是字符串
foo2([{b:{c:1,d:'}}],a=>a.b)(“c”,v=>{});//v是数字

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

如果数组元素具有不同类型的属性,则此解决方案会进行编译,但v的推断类型将不会绑定到属性名称。例如:对于
foo5([{b:{c:1,d:“}}],a=>a.b,“c”,v=>{})
v
将是
number | string
当它应该是
number
时,实际上,在具有多个属性类型的更复杂的
记录的情况下,返回类型将变为
any
,如果您关心支持哪些用例,那么将它们包含在问题文本中会有所帮助
任何
类型都可能是由于与使用诸如
o=>o等getter相关的编译器错误导致的。这是我的一个用例,但可以为该用例创建一个简化版本的函数。但是,@TitianCernicova Dragomir的观点仍然站得住脚。我已经更新了签名以支持该用例。如果您有更多需要支持的内容,请更新您的问题文本,以便其他想要回答的人可以在问题中看到问题的全部范围,而不是通过评论阅读。谢谢
foo([{ b: { c: 1, d: "" } }], a => a.b, "c", (v: number): void => { });
foo<{ b: { c: number; d: string; }; }, "c", number>(...)
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