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

Correct type inference with keyof in generic functions

我需要编写通用函数,将对象类型的键子集中的对象和键作为参数,这些键对应于指定类型的值。

我尝试实现如下。

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
}

但是编译器给出错误消息 "Property 'length' does on exist on type 'T[{ [P in keyof T]: T[P] extends TProp ? P : never }[keyof T]]'"。

为什么编译器不认为我有有限的一组可能的键,只有对应于字符串类型值的键?如何解决?

编译器不够聪明。依赖于通用参数的条件类型(如 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;
}

它类似于类型断言的原因是因为编译器允许您使实现签名比调用签名更宽松...如果您不小心,您可能会骗过编译器而您不会直到运行时才看到问题。


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