在 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. 總結
- 分布式條件類型的核心作用:
- 對聯合類型的每個成員逐個應用條件類型。
- 支持靈活地過濾、轉換和操作聯合類型。
- 關鍵點:
- 裸類型參數是觸發分發的關鍵。
- 可以通過包裹類型參數阻止分發。
- 實際場景:
- 過濾聯合類型。
- 提取鍵名或特定類型的成員。
- 動態轉換聯合類型的成員。