保留通用变量之间的类型限制
Preserve Type Restriction between Generic Variables
假设我们有一些 class 有一个重要的通用变量 T
和另一个 class 我们有两个字段,一个包装,一个不包装:
class Wrapper<V> {
constructor(public value: V) {
}
clone(): Wrapper<V> {
return new Wrapper(this.value);
}
}
class SomeClass {
value1 = new Wrapper(1);
value2 = 2;
}
然后,我们想要一个方法wrapperValue
,当给定一个对象(obj
)和一个字段名(name
)时,returns wrapper 的值obj[name].value
访问。 return 类型正确很重要。到目前为止,这是我设法想出的:
type WrapperKeyOf<S> = keyof {
[K in keyof S as S[K] extends Wrapper<any> ? K: never]: any
}
type WrapperValueTypeOf<W> = W extends Wrapper<infer V> ? V : never;
function wrapperValue<S, K extends WrapperKeyOf<S>>(
obj: S,
name: K,
): WrapperValueTypeOf<S[K]> {
const wrapper: Wrapper<WrapperValueTypeOf<S[K]>> = obj[name];
return wrapper.value;
}
wrapperValue(new SomeClass(), "value1");
类型 WrapperKeyOf
将 name
限制为仅作为 S
的键,其中 S[T]
是 Wrapper
,并且 WrapperValueTypeOf<S[T]>
得到包装类型。
TypeScript 编译器产生以下错误:
Type 'S[K]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
Type 'S[keyof { [K in keyof S as S[K] extends Wrapper<any> ? K : never]: any; }]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
Type 'S[string] | S[number] | S[symbol]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
Type 'S[string]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
似乎 K
必须是访问 Wrapper
的 S
的密钥这一事实丢失了。有什么办法可以以某种方式保存这些信息吗?
不幸的是,编译器无法执行那种抽象 generic type analysis necessary to verify that T[KeysMatching<T, V>]
is assignable to V
for generic T
, where KeysMatching<T, V>
is the union of property keys of T
whose property values are assignable to V
, as described in . The problem is that KeysMatching<T, V>
can only be implemented with a conditional type (somewhere in there you'll have a check like T[K] extends V ? K : never
), and the compiler essentially treats conditional types that depend on generic type parameters as opaque, and chooses to defer evalutation of them until the generic type parameters are specified with some specific type. This is effectively a design limitation of TypeScript, and is documented at microsoft/TypeScript#30728 and microsoft/TypeScript#31275(可能还有其他)。
由于您的 WrapperKeyOf<T>
是 KeysMatching<T, Wrapper<any>>
的实现,这意味着编译器看不到 T[WrapperKeyOf<T>]
可分配给 Wrapper<any>
。
但是,编译器 可以 告诉你,当你 index into a mapped type of the form {[P in K]: V}
(or the equivalent use of the Record<K, V>
utility type) 使用键 K
时,你将获得可分配给 [=13= 的东西].
因此,如果您根据约束 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
可以是任何 key-like 类型,然后我们限制 obj
是有键的东西 K
并且该键的值是泛型类型参数 V
的 Wrapper<V>
类型。现在编译器知道 obj[name].value
是 V
类型,所以实现是 error-free.
并且您对 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'
假设我们有一些 class 有一个重要的通用变量 T
和另一个 class 我们有两个字段,一个包装,一个不包装:
class Wrapper<V> {
constructor(public value: V) {
}
clone(): Wrapper<V> {
return new Wrapper(this.value);
}
}
class SomeClass {
value1 = new Wrapper(1);
value2 = 2;
}
然后,我们想要一个方法wrapperValue
,当给定一个对象(obj
)和一个字段名(name
)时,returns wrapper 的值obj[name].value
访问。 return 类型正确很重要。到目前为止,这是我设法想出的:
type WrapperKeyOf<S> = keyof {
[K in keyof S as S[K] extends Wrapper<any> ? K: never]: any
}
type WrapperValueTypeOf<W> = W extends Wrapper<infer V> ? V : never;
function wrapperValue<S, K extends WrapperKeyOf<S>>(
obj: S,
name: K,
): WrapperValueTypeOf<S[K]> {
const wrapper: Wrapper<WrapperValueTypeOf<S[K]>> = obj[name];
return wrapper.value;
}
wrapperValue(new SomeClass(), "value1");
类型 WrapperKeyOf
将 name
限制为仅作为 S
的键,其中 S[T]
是 Wrapper
,并且 WrapperValueTypeOf<S[T]>
得到包装类型。
TypeScript 编译器产生以下错误:
Type 'S[K]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
Type 'S[keyof { [K in keyof S as S[K] extends Wrapper<any> ? K : never]: any; }]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
Type 'S[string] | S[number] | S[symbol]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
Type 'S[string]' is not assignable to type 'Wrapper<WrapperValueTypeOf<S[K]>>'.
似乎 K
必须是访问 Wrapper
的 S
的密钥这一事实丢失了。有什么办法可以以某种方式保存这些信息吗?
不幸的是,编译器无法执行那种抽象 generic type analysis necessary to verify that T[KeysMatching<T, V>]
is assignable to V
for generic T
, where KeysMatching<T, V>
is the union of property keys of T
whose property values are assignable to V
, as described in KeysMatching<T, V>
can only be implemented with a conditional type (somewhere in there you'll have a check like T[K] extends V ? K : never
), and the compiler essentially treats conditional types that depend on generic type parameters as opaque, and chooses to defer evalutation of them until the generic type parameters are specified with some specific type. This is effectively a design limitation of TypeScript, and is documented at microsoft/TypeScript#30728 and microsoft/TypeScript#31275(可能还有其他)。
由于您的 WrapperKeyOf<T>
是 KeysMatching<T, Wrapper<any>>
的实现,这意味着编译器看不到 T[WrapperKeyOf<T>]
可分配给 Wrapper<any>
。
但是,编译器 可以 告诉你,当你 index into a mapped type of the form {[P in K]: V}
(or the equivalent use of the Record<K, V>
utility type) 使用键 K
时,你将获得可分配给 [=13= 的东西].
因此,如果您根据约束 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
可以是任何 key-like 类型,然后我们限制 obj
是有键的东西 K
并且该键的值是泛型类型参数 V
的 Wrapper<V>
类型。现在编译器知道 obj[name].value
是 V
类型,所以实现是 error-free.
并且您对 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'