在 TypeScript 中,分发逆变推断(Distributive and Contravariant Inference) 是一个相对高级且复杂的概念,涉及到分布式条件类型和逆变(Contravariance)的结合。理解这个概念需要对以下三个核心知识点有清晰的认识:
- 分布式条件类型(Distributive Conditional Types)
- 逆变(Contravariance)
infer
关键字
1. 分布式条件类型
(1) 定义
- 当条件类型作用于一个裸类型参数(Naked Type Parameter),并且该参数是一个联合类型时,TypeScript 会将条件类型“分布”到联合类型的每个成员上。
- 这种行为称为分布式条件类型。
(2) 示例
type IsString<T> = T extends string ? true : false;
type Result = IsString<string | number>; // true | false
在这里:
string | number
被分发为string
和number
。- 条件类型分别应用于
string
和number
,最终结果是true | false
。
2. 逆变(Contravariance)
(1) 定义
- 逆变是指在函数参数中,子类型关系的方向与返回值相反。
- 如果类型
A
是类型B
的子类型,则函数(arg: B) => void
是(arg: A) => void
的子类型。
(2) 示例
type Func<T> = (arg: T) => void;
const anyFunc: Func<any> = (arg: any) => console.log(arg);
const stringFunc: Func<string> = anyFunc; // 合法:逆变
在这里:
- 函数参数是逆变的,因此
Func<any>
是Func<string>
的子类型。
3. 分发逆变推断
当分布式条件类型与逆变结合时,可能会出现一些复杂的行为。这种现象被称为分发逆变推断。
示例 1:函数参数的分布式条件类型
type ParametersFromFunction<T> = T extends (arg: infer P) => any ? P : never;
type Func1 = ParametersFromFunction<(arg: string) => void>; // string
type Func2 = ParametersFromFunction<(arg: number) => void>; // number
type UnionFunc = ParametersFromFunction<((arg: string) => void) | ((arg: number) => void)>; // string & number
在这里:
ParametersFromFunction
使用infer P
提取了函数参数的类型。- 对于单个函数类型,结果是参数类型本身(如
string
或number
)。 - 对于联合类型
(arg: string) => void | (arg: number) => void
,参数类型被推断为string & number
。
这是因为:
- 函数参数是逆变的,联合类型的逆变会导致参数类型变为所有可能类型的交集(
string & number
)。
示例 2:数组元素的分布式条件类型
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Numbers = ArrayElement<number[]>; // number
type Strings = ArrayElement<string[]>; // string
type MixedArray = ArrayElement<(string | number)[]>; // string | number
在这里:
- 数组元素是协变的,因此联合类型的分发结果是
string | number
。
4. 实际应用场景
(1) 提取函数参数类型
你可以使用分布式条件类型和逆变推断提取函数参数的类型。
示例代码
type ParametersFromFunction<T> = T extends (arg: infer P) => any ? P : never;
type FuncUnion = ParametersFromFunction<((arg: string) => void) | ((arg: number) => void)>; // string & number
在这里:
- 函数参数的逆变导致联合类型的推断结果是交集类型(
string & number
)。
(2) 处理多态函数
你可以使用分发逆变推断来处理多态函数的参数类型。
示例代码
type Func<T> = (arg: T) => void;
type InferParameters<T> = T extends Func<infer P> ? P : never;
type Result = InferParameters<Func<string> | Func<number>>; // string & number
在这里:
InferParameters
使用infer P
提取了函数参数的类型。- 因为函数参数是逆变的,联合类型的推断结果是交集类型(
string & number
)。
5. 注意事项
(1) 交集类型的意义
- 在逆变推断中,联合类型的参数会被推断为交集类型。
- 这是因为逆变要求参数类型必须兼容所有可能的情况。
示例
type FuncUnion = ((arg: string) => void) | ((arg: number) => void);
type Parameters = ParametersFromFunction<FuncUnion>; // string & number
在这里:
- 参数类型
string & number
表示函数可以接受同时兼容string
和number
的值(即never
)。
(2) 避免过度复杂
- 分发逆变推断的结果可能会导致交集类型变得难以理解。
- 在实际开发中,尽量避免过于复杂的类型操作。
6. 总结
- 分发逆变推断的核心作用:
- 结合分布式条件类型和逆变的行为,动态推断出参数或返回值的类型。
- 支持灵活的类型操作,特别是在处理函数联合类型时。
- 常见场景:
- 提取函数参数类型。
- 处理多态函数。
- 注意事项:
- 理解逆变对联合类型的影响(交集类型)。
- 避免过度复杂化类型定义。