通用 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] 之前被检查=] 参数。 (我不认为 Vstring | 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 类似,我不一定有原因,只是一种解决方法。如果您以两次调用的方式进行,您可以在第一次调用中修复 AB,然后在第二次调用中发送 keycallback,然后类型将被正确推断。

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

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