在泛型函数中使用keyof纠正类型推断

时间:2019-03-17 17:11:04

标签: typescript generics types type-inference

我需要编写一个泛型函数,该函数将对象类型的键子集中对应于指定类型值的对象和键作为参数。

我尝试如下实现。

type KeysOfType<T, TProp> = { [P in keyof T]: T[P] extends TProp ? P : never }[keyof T];

function getLen<T>(obj: T, p: KeysOfType<T, string>): number {
    return obj[p].length
}

但是编译器给出了一条错误消息,消息“类型'T [{[T中的P]中存在属性'长度':T [P]扩展了TProp?P:从不} [keyof T]]'”

为什么编译器不认为我只有有限的一组可能的键,而只有与类型为string的值相对应的键?如何解决?

1 个答案:

答案 0 :(得分:2)

编译器不够聪明。依赖于通用参数的条件类型(例如KeysOfType<T, string>)通常被编译器视为不透明,而 you 理解KeysOfType<T, V>是专门构造的,以确保T[KeysOfType<T, V>] extends V是正确的,编译器甚至不会尝试。

在这种情况下,我们可以使用的最通用的解决方案是使用type assertion。例如,您可以告诉编译器不要担心,并将obj[p]视为string

function getLen<T>(obj: T, p: KeysOfType<T, string>): number {
    return (obj[p] as unknown as string).length;
    // the type T[{ [P in keyof T]: T[P] extends string ? P : never; }[keyof T]]
    // is so opaque to the compiler that we must widen to unknown 
    // before narrowing to string
}

请注意,您将减轻编译器验证类型安全性的责任。您可以很容易地说出obj[p] as unknown as boolean,编译器就会相信您。因此,请谨慎使用此功能。


做类似事情的另一种方法是使用单个function overload来区分调用者看到的通用条件类型和实现中希望更易处理的类型:

// call signature, unchanged
function getLen<T>(obj: T, p: KeysOfType<T, string>): number;

// implementation signature... let's make p the generic type K
// and say that obj has keys K and values of type string
function getLen<K extends keyof any>(obj: Record<K, string>, p: K): number {
  return obj[p].length;
}

与类型断言类似的原因是,编译器使您可以使实现签名比调用签名更松散……如果您不小心,可以躺在编译器上,并且看不到问题直到运行时。


好的,希望能有所帮助。祝你好运!