在 TypeScript 中,分布式条件类型(Distributive Conditional Types) 是一种特殊的行为,发生在条件类型作用于裸类型参数(Naked Type Parameter) 时。这种特性使得条件类型可以“分布”到联合类型的每个成员上,从而对联合类型进行逐个处理。
1. 分布式条件类型的基本概念
(1) 定义
- 当条件类型作用于一个裸类型参数(即没有被包裹的泛型参数),并且该参数是一个联合类型时,TypeScript 会将条件类型“分发”到联合类型的每个成员上。
- 这种行为称为分布式条件类型。
(2) 语法
type Distributed<T> = T extends U ? X : Y;
- 如果
T
是一个联合类型(如A | B | C
),则条件类型会分别对A
、B
和C
应用T extends U ? X : Y
,并将结果重新组合成一个新的联合类型。
2. 示例:分布式条件类型的作用
示例 1:基本行为
type IsString<T> = T extends string ? true : false;
type Result = IsString<string | number>; // true | false
在这里:
string | number
是一个联合类型。- 条件类型
IsString
会被“分发”到string
和number
上:- 对
string
:string extends string ? true : false
→true
- 对
number
:number extends string ? true : false
→false
- 对
- 最终结果是
true | false
。
示例 2:提取字符串类型
type ExtractStrings<T> = T extends string ? T : never;
type Result = ExtractStrings<string | number | boolean>; // string
在这里:
ExtractStrings
会逐个检查联合类型的每个成员:- 对
string
:string extends string ? string : never
→string
- 对
number
:number extends string ? number : never
→never
- 对
boolean
:boolean extends string ? boolean : never
→never
- 对
- 最终结果是
string
。
3. 阻止分布式条件类型
如果不想让条件类型“分布”,可以通过将裸类型参数包裹起来(例如使用数组或元组)来阻止分发。
示例:阻止分发
type IsString<T> = [T] extends [string] ? true : false;
type Result = IsString<string | number>; // false
在这里:
[T]
将T
包裹在数组中,阻止了分发。- 整个联合类型
string | number
被视为一个整体,而不是逐个成员处理。 - 因为
string | number
不是string
的子类型,所以结果是false
。
4. 实际应用场景
分布式条件类型在实际开发中有许多用途,以下是一些常见的场景:
(1) 过滤联合类型
你可以使用分布式条件类型从联合类型中提取满足条件的成员。
示例:过滤出可调用的类型
type FilterCallable<T> = T extends (...args: any[]) => any ? T : never;
type MixedTypes = string | (() => void) | number | ((x: number) => number);
type CallableTypes = FilterCallable<MixedTypes>; // () => void | ((x: number) => number)
在这里:
FilterCallable
提取了所有可调用的类型。
(2) 提取键名
你可以使用分布式条件类型提取对象的键名。
示例:提取值为字符串的键
type KeysOfType<T, U> = {
[K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
type Data = {
name: string;
age: number;
email: string;
};
type StringKeys = KeysOfType<Data, string>; // "name" | "email"
在这里:
KeysOfType
提取了值类型为string
的键名。
(3) 类型转换
你可以使用分布式条件类型对联合类型的每个成员进行转换。
示例:为每个成员添加前缀
type AddPrefix<T, Prefix extends string> = T extends string
? `${Prefix}${T}`
: never;
type Colors = "red" | "green" | "blue";
type PrefixedColors = AddPrefix<Colors, "color_">; // "color_red" | "color_green" | "color_blue"
在这里:
AddPrefix
为联合类型的每个成员添加了前缀。
5. 注意事项
(1) 裸类型参数是关键
- 分布式条件类型只会在裸类型参数上触发。
- 如果类型参数被包裹(例如放在数组或元组中),分发行为会被阻止。
示例:裸类型与包裹类型的区别
type Distribute<T> = T extends string ? true : false;
type NoDistribute<T> = [T] extends [string] ? true : false;
type Result1 = Distribute<string | number>; // true | false
type Result2 = NoDistribute<string | number>; // false
(2) 避免意外分发
- 如果不希望触发分发,可以通过包裹类型参数来显式阻止。
6. 总结
- 分布式条件类型的核心作用:
- 对联合类型的每个成员逐个应用条件类型。
- 支持灵活地过滤、转换和操作联合类型。
- 关键点:
- 裸类型参数是触发分发的关键。
- 可以通过包裹类型参数阻止分发。
- 实际场景:
- 过滤联合类型。
- 提取键名或特定类型的成员。
- 动态转换联合类型的成员。