我需要编写泛型函数,它从对象类型的键子集中获取作为参数的对象和键,它对应于指定类型的值。
我尝试按如下方式实现它。
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
}
但是compiller给出了一个错误,消息“属性'长度'确实存在于类型'T [{[P in keyof T]:T [P]扩展TProp?P:never} [keyof T]]'”。
为什么编译器不认为我只有一组可能的键只有与string类型值对应的键?怎么解决?
编译器不够聪明。依赖于通用参数的条件类型(如KeysOfType<T, string>
)通常被编译器视为有些不透明,虽然您了解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;
}
它类似于类型断言的原因是因为编译器允许你使实现签名比调用签名更松散...如果你不小心你可以欺骗编译器,你就不会看到问题直到运行时。
好的,希望有所帮助。祝好运!