跳至主要內容

條件類型

Flow 的條件類型允許您透過檢查輸入類型,在兩種不同的輸出類型之間有條件地選擇。這對於擷取類型的部分,或描述複雜的重載很有用。

基本用法

它的語法類似於條件運算式:CheckType extends ExtendsType ? TrueType : FalseType

如果 CheckTypeExtendsType 的子類型,則條件類型將評估為 TrueType。否則,它將評估為 FalseType

以下範例說明了這兩種情況。

1class Animal {}2class Dog extends Animal {}3
4type TypeofAnimal = Dog extends Animal ? 'animal' : 'unknown'; // evaluates to 'animal'5type TypeofString = string extends Animal ? 'animal' : 'unknown'; // evaluates to 'unknown'

通用條件類型

這看起來可能不太有用,因為您已經知道它將評估為哪種類型。然而,結合泛型,您可以對類型執行複雜的運算。例如,您可以寫下類型級別的 typeof 運算子

1type TypeOf<T> =2  T extends null ? 'null' :3  T extends void ? 'undefined' :4  T extends string ? 'string' :5  T extends number ? 'number' :6  T extends boolean ? 'boolean' :7  T extends (...$ReadOnlyArray<empty>)=>mixed ? 'function' : 'object'8
9type T1 = TypeOf<null>; // evaluates to 'null'10type T2 = TypeOf<void>; // evaluates to 'undefined'11type T3 = TypeOf<string>; // evaluates to 'string'12type T4 = TypeOf<number>; // evaluates to 'number'13type T5 = TypeOf<boolean>; // evaluates to 'boolean'14type T6 = TypeOf<(string)=>boolean>; // evaluates to 'function'15type T7 = TypeOf<{foo: string}>; // evaluates to 'object'

函式回傳類型取決於輸入類型

條件類型也允許您直觀地描述選擇不同函式重載的條件

1declare function wrap<T>(value: T): T extends string ? { type: 'string', value: string }2                                  : T extends number ? { type: 'number', value: number }3                                  : { type: 'unsupported' }4
5const v1 = wrap(3);   // has type { type: 'number', value: number }6const v2 = wrap('4'); // has type { type: 'string', value: string }7const v3 = wrap({});  // has type { type: 'unsupported' }

上述範例也可以使用函式重載來撰寫

1declare function wrap(value: string): { type: 'string', value: string }2declare function wrap(value: number): { type: 'number', value: number }3declare function wrap(value: mixed): { type: 'unsupported' }4
5const v1 = wrap(3);   // has type { type: 'number', value: number }6const v2 = wrap('4'); // has type { type: 'string', value: string }7const v3 = wrap({});  // has type { type: 'unsupported' }

在條件類型中推論

您可以使用條件類型的功能來使用 infer 類型萃取類型的一部分。例如,內建的 ReturnType 是由條件類型提供支援的

1type ReturnType<T> = T extends (...args: $ReadOnlyArray<empty>) => infer Return ? Return : empty;2
3type N = ReturnType<(string) => number>; // evaluates to `number`4type S = ReturnType<(number) => string>; // evaluates to `string`

我們在此處使用推論類型來引入一個名為 Return 的新泛型類型變數,它可以在條件類型的類型分支中使用。推論類型只能出現在條件類型中 extends 子句的右側。Flow 將執行檢查類型和延伸類型之間的子類型檢查,以根據輸入類型 T 自動找出其類型。

type N = ReturnType<(string) => number> 的範例中,Flow 檢查 (string) => number 是否為 (...args: $ReadOnlyArray<empty>) => infer Return 的子類型,在此過程中,Return 被限制為 number

在執行上述範例的萃取時,您通常希望條件類型始終選擇成功萃取類型的真分支。例如,靜默選擇假分支並不好

1type ExtractReturnTypeNoValidation<T> =2  T extends (...args: $ReadOnlyArray<empty>) => infer Return ? Return : any;3
41 as ExtractReturnTypeNoValidation<string>; // no error :(

相反地,您可能希望 Flow 在輸入不是函式類型時產生錯誤。這可以透過新增約束到類型參數來達成

1type ReturnType<T: (...args: $ReadOnlyArray<empty>) => mixed> =2  T extends (...args: $ReadOnlyArray<empty>) => infer Return ? Return : any;3
41 as ReturnType<(string) => number>;51 as ReturnType<string>;
5:17-5:22: Cannot instantiate `ReturnType` because string [1] is incompatible with function type [2] in type argument `T`. [incompatible-type-arg]

分配條件類型

當泛型條件類型給定一個聯合類型作為類型引數時,條件會分配到聯合的成員。例如,上述的 TypeOf 範例可以分配到一個聯合

1type TypeOf<T> =2  T extends null ? 'null' :3  T extends void ? 'undefined' :4  T extends string ? 'string' : 'other';5
6type StringOrNull = TypeOf<string | null>; // evaluates to 'string' | 'null'

這會先分解聯合類型,然後將每個類型傳遞到條件類型以分別評估。在上述範例中,這看起來像

TypeOf<string | null>
--> (break up the union) --> TypeOf<string> | TypeOf<null>
--> (evaluate each conditional type separately) --> 'string' | 'null'

如果您想避免這種行為,您可以使用一元組類型包裝檢查類型和擴充類型

1type NonDistributiveTypeOf<T> =2  [T] extends [null] ? 'null' :3  [T] extends [void] ? 'undefined' :4  [T] extends [string] ? 'string' : 'other';5
6type Other = NonDistributiveTypeOf<string | null>; // evaluates to 'other'

此技巧之所以有效,是因為只有在檢查類型為泛型類型時,Flow 才會啟用條件類型的分配行為。上面的範例未選擇條件類型的任何 true 分支,因為 [string | null] 不是 [null][void][string] 的子類型,因為元組是 不變 類型。

採用

若要使用條件類型,您需要升級您的基礎架構,以支援語法

  • flowflow-parser:0.208.0。在 v0.208 至 v0.211.1 之間,您需要在 .flowconfig 中的 [options] 標題下明確啟用它,新增 conditional_type=true
  • prettier: 3
  • babel 搭配 babel-plugin-syntax-hermes-parser。請參閱 我們的 Babel 指南 以取得設定說明。
  • eslint 搭配 hermes-eslint。請參閱 我們的 ESLint 指南 以取得設定說明。