不幸的是编译器无法执行这种抽象generic需要进行类型分析来验证T[KeysMatching<T, V>]
可分配给V
对于通用的T
, where KeysMatching<T, V>
是属性键的并集T
其属性值可分配给V
,如中所述这个问题。问题是KeysMatching<T, V>
只能用一个来实现条件类型(在那里的某个地方你会有一张支票,比如T[K] extends V ? K : never
),并且编译器本质上将依赖于泛型类型参数的条件类型视为opaque,并选择defer对它们进行评估,直到用某种特定类型指定泛型类型参数。这实际上是 TypeScript 的设计限制,记录在微软/TypeScript#30728 and 微软/TypeScript#31275(可能还有其他)。
自从你的WrapperKeyOf<T>
是一个实现KeysMatching<T, Wrapper<any>>
,这意味着编译器看不到T[WrapperKeyOf<T>]
将可分配给Wrapper<any>
.
然而,编译器can告诉你,当你index into a 映射类型形式的{[P in K]: V}
(或等效使用the Record<K, V>实用型)用钥匙K
你会得到一些可分配给的东西V
.
因此,如果您根据约束重新表述您的要求obj
而不是限制name
,您将能够获得您正在寻找的类型安全保证:
function wrapperValue<V, K extends PropertyKey>(
obj: Record<K, Wrapper<V>>,
name: K,
): V {
const wrapper = obj[name];
return wrapper.value; // okay
}
在这里我们让name
属于泛型类型K
可以是任何类似键的类型,然后我们限制obj
成为有钥匙的东西K
并且该键的值是类型Wrapper<V>
对于泛型类型参数V
。现在编译器知道obj[name].value
属于类型V
,所以执行是没有错误的。
还有您的来电wrapperValue()
仍然是安全的(尽管当你犯错时,错误现在会出现obj
代替name
):
const result = wrapperValue(new SomeClass(), "value1"); // okay
console.log(result.toFixed(1)); // 1.0
wrapperValue(new SomeClass(), "value2"); // error!
// --------> ~~~~~~~~~~~~~~~
// Type 'number' is not assignable to type 'Wrapper<number>'
wrapperValue(new SomeClass(), "value3"); // error!
// --------> ~~~~~~~~~~~~~~~
// Property 'value3' is missing in type 'SomeClass'
Playground 代码链接