本文へスキップ

条件付き型

Flowの条件付き型を使用すると、入力型を検査することで、2つの異なる出力型のいずれかを選択できます。これは、型の部分を抽出したり、複雑なオーバーロードを記述するのに役立ちます。

基本的な使用方法

構文は条件式に似ています: 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`

ここでは、infer型を使用して、条件付き型の型ブランチで使用できる新しいジェネリック型変数Returnを導入しました。infer型は、条件付き型のextends句の右側でのみ使用できます。Flowは、チェック型とextends型の間のサブタイプチェックを実行して、入力型Tに基づいてその型を自動的に判断します。

type N = ReturnType<(string) => number>の例では、Flowは(string) => number(...args: $ReadOnlyArray<empty>) => infer Returnのサブタイプであるかどうかをチェックし、このプロセス中にReturnnumberに制約されます。

上記の例のような抽出を行う場合、通常は型が正常に抽出されたtrueブランチを条件付き型が常に選択することを望みます。たとえば、falseブランチを黙って選択することは適切ではありません。

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'

この動作を回避するには、チェック型とextends型を単項タプル型でラップします。

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が条件付き型の分散動作を有効にするため機能します。上記の例では、タプルは不変に型付けされているため、[string | null][null][void]、または[string]のサブタイプではないため、条件付き型のtrueブランチは選択されません。

導入

条件付き型を使用するには、構文をサポートするようにインフラストラクチャをアップグレードする必要があります。

  • flowflow-parser: 0.208.0。v0.208からv0.211.1の間は、.flowconfig[options]セクションでconditional_type=trueを明示的に有効にする必要があります。
  • prettier: 3
  • babel-plugin-syntax-hermes-parserを使用したbabel。設定手順については、Babelガイドを参照してください。
  • hermes-eslintを使用したeslint。設定手順については、ESLintガイドを参照してください。