获取以下代码:
declare class BaseClass<TValue = any> {
value: TValue;
foo(value: TValue): void;
}
type Wrapped<T> = { value: T }
declare class ConcreteClasss<TValue> extends BaseClass<TValue> {
constructor(value: Wrapped<TValue> | TValue);
override foo(value: TValue | Wrapped<TValue>): void;
}
const valuesOf = <V>(base: BaseClass<V>): V => base.value;
const base = new ConcreteClasss('Alma');
//^?
const value = base.value;
//^? string
const value2 = valuesOf(base);
//^? string | Wrapped<string>
显然,
foo
方法用于推理站点(为什么不呢)。
但是为什么当通过辅助函数访问值时,值的类型会不同?
当您调用
valuesOf(base)
时,TypeScript 需要通过将 V
类型的值视为 ConcreteClass<string>
类型的值来推断 BaseClass<V>
。它本质上与type V = ConcreteClass<string> extends BaseClass<infer V> ? V : never
相同。 这是 string | Wrapped<string>
,因为正如您所说,TypeScript 从 V
推断出 foo
(可能是因为方法的参数是双变的?如果您将 foo
定义为函数属性而不是方法,那么推断几乎将当然是不变的,你会得到string
,但我认为这超出了所问问题的范围)。 这些类型根本不再记得ConcreteClass<T>
;这些信息已被丢弃。因此,即使 valuesOf
的实现写为 base.value
,唯一可用的类型是 V
,即 string | WrappedString<T>
。
如果你想让
valuesOf(base)
返回base.value
的实际类型,你需要将函数generic设为base
的类型,例如:
const valuesOf =
<B extends BaseClass<any>>(base: B): B['value'] => base.value
我已经注释了函数的返回类型以使用索引访问类型
B['value']
,否则你可能会得到BaseClass<any>['value']
或any
,因为TS将特定索引扩展到通用对象到约束,请参阅 microsoft/TypeScript#33181。
现在你明白了
const value2 = valuesOf(base);
// ^? const value2: string;
随心所欲。