メインコンテンツにスキップ

型ガード

Flow では、関数の戻り式が、パラメータ param に対する型述語をエンコードするような関数を定義できます。この述語は、戻り値の型注釈の代わりに param is PredicateType として注釈されます。これは、関数が true を返した場合、paramPredicateType 型であることを宣言します。

このような関数の構文は次のとおりです。

function predicate(param: InputType): param is PredicateType {
return <some_expression>;
}

この関数の型は、型ガード注釈で記述することもできます。

type PredicateFunc = (param: InputType) => param is PredicateType;

基本的な使い方

型ガード関数を定義し、それを使用していくつかの値を絞り込む簡単な例を見てみましょう。

型ガード関数の定義

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}

ここでは、それぞれがタグとして使用される type プロパティを持つ 2 つの型 AB の非交和であるデータ型 AorB を定義しました。

また、AorB 型のオブジェクトに対して定義された、ユーザー定義の型ガード 関数 isA を記述しました。この関数は、入力の type プロパティの値が "A" の場合に true を返します。AB の定義を使用すると、Flow は、type の値が "A" の場合、value の型が A になることを証明できます。

型ガード関数を使用して値を絞り込む

宣言された型ガードを持つ関数は、条件式で値を絞り込むために使用できます。上記の例では、isA を使用して、AorB 型の変数を単に A に絞り込むことができます。

1type A = { type: "A"; data: string };2type B = { type: "B"; data: number };3type AorB = A | B;4
5function isA(value: AorB): value is A {6  return value.type === "A";7}8
9function test(x: AorB) {10  if (isA(x)) {11    // `x` has now been refined to type A.12    // We can assign it variables of type A ...13    const y: A = x;14    // ...and access A's properties through `x`15    const stringData: string = x.data;16
17    // As a sanity check, the following assignment to B will error18    const error: B = x;
19 }20}
18:22-18:22: Cannot assign `x` to `error` because string [1] is incompatible with number [2] in property `data`. [incompatible-type]
18:22-18:22: Cannot assign `x` to `error` because string literal `A` [1] is incompatible with string literal `B` [2] in property `type`. [incompatible-type]

条件式 if (isA(x)) の then ブランチでは、xA 型になります。

Array.filter で絞り込む

Flow は、型 (value: T) => value is S の型ガードを持つコールバック関数で、Array<T> 型の配列に対して filter を呼び出したときに認識します。これは、Array<S> 型の出力配列を生成するために使用されます。S は、配列要素 T の型のサブタイプである必要があることに注意してください。

例を挙げます。

1type Success = $ReadOnly<{type: 'success', value: 23}>;2type Error = $ReadOnly<{type: 'error', error: string}>;3
4type Response =5  | Success6  | Error7
8function filterSuccess(response: Array<Response>): Array<Success> {9  return response.filter(10    (response): response is Success => response.type === 'success'11  );12}13
14function filterError1(response: Array<Response>): Array<Error> {15  const result = response.filter(16    (response): response is Success => response.type === 'success'17  );18  // The following is expected to error19  return result;
20}21 22function filterError2(response: Array<Response>): Array<Error> {23 const result = response.filter(24 // The following is expected to error25 (response): response is Error => response.type === 'success'
26 );27 return result;28}
19:10-19:15: Cannot return `result` because property `error` is missing in object type [1] but exists in object type [2] in array element. [prop-missing]
19:10-19:15: Cannot return `result` because property `value` is missing in object type [1] but exists in object type [2] in array element. [prop-missing]
19:10-19:15: Cannot return `result` because string literal `success` [1] is incompatible with string literal `error` [2] in property `type` of array element. [incompatible-return]
25:38-25:64: Cannot return `response.type === 'success'` because property `error` is missing in object type [1] but exists in object type [2] in the type inferred for type guard parameter `response` [3]. [prop-missing]
25:38-25:64: Cannot return `response.type === 'success'` because property `value` is missing in object type [1] but exists in object type [2] in the type inferred for type guard parameter `response` [3]. [prop-missing]
25:38-25:64: Cannot return `response.type === 'success'` because string literal `success` [1] is incompatible with string literal `error` [2] in property `type` of the type inferred for type guard parameter `response` [3]. [incompatible-return]

filterError1 では、フィルタリングによって、予想される戻り値の型 Array<Error> と互換性のない Array<Success> が生成されます。

filterError2 では、述語 response.type === 'success'ResponseError ではなく Success に絞り込むために使用されます。

型ガード関数の定義

型ガード関数による絞り込みが健全であることを保証するために、Flow はこれらの関数に関連付けられたいくつかのチェックを実行します。

述語パラメータは関数の通常のパラメータです

parameter is Type の形式の型ガード注釈では、parameter は現在の関数のパラメータリストに属している必要があります。

1function missing(param: mixed): prop is number {
2 return typeof param === "number";3}
1:33-1:36: Cannot find type guard parameter `prop` [1] in the parameters of this function (type). [function-predicate]

分割代入パターンまたは残余パラメータでバインドされたパラメータにすることはできません。

1function destructuring({prop}: {prop: mixed}): prop is number {
2 return typeof prop === "number";3}
1:48-1:51: A type guard parameter `prop` [1] cannot reference pattern parameter `prop` [2]. [function-predicate]
1function rest(...value: Array<mixed>): value is Array<mixed> {
2 return Array.isArray(value);3}
1:40-1:44: A type guard parameter `value` [1] cannot reference rest parameter `value` [2]. [function-predicate]

述語型はパラメータ型と一致します

型ガード Type は、パラメータの型と互換性がある必要があります。言い換えれば、定義が与えられた場合

function isT(x: ParamType): x is Type {
return ...
}

Flow は、TypeParamType のサブタイプであることを確認します。したがって、次はエラーになります。

1function isNumber(x: string): x is number {
2 return typeof x === "number";3}
1:36-1:41: Cannot use number [1] as type prediate for parameter `x` because number [1] is incompatible with string [2]. A user defined type guard needs to be compatible with its parameter's type. [incompatible-type-guard]

型ガード関数はブール値を返します

型ガード関数は、ブール式を返す必要があります。次は無効な宣言です。

1function isNumberNoReturn(x: string): x is string {}
1:39-1:49: Cannot declare a type predicate [1] for function [2] because boolean [1] is incompatible with implicitly-returned undefined. [incompatible-return]
1function nonMaybe<V: {...}>(x: ?V): x is V {2  return x;
3}
2:10-2:10: Cannot return `x` because null or undefined [1] is incompatible with boolean [2]. [incompatible-return]
2:10-2:10: Cannot return `x` because object type [1] is incompatible with boolean [2]. [incompatible-return]

nonMaybe の正しいバージョンは次のようになります。

1function nonMaybe<V: {...}>(x: ?V): x is V {2  return !!x;3}

述語型は絞り込まれた型と一致します

上記のチェックに加えて、Flow は、宣言された型ガードが関数の本体で行われているチェックと一貫性があることも保証します。これを確立するために、次の 2 つのことを保証する必要があります。

  1. 戻り式が適用されたの戻り位置での絞り込まれたパラメータの型が、ガード型のサブタイプであること。たとえば、次の定義は正しいです。
1function numOrStr(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "string");3}4
5function numOrStrWithException(x: mixed): x is number | string {6  if (typeof x === "number") {7    return true;8  } else {9    if (typeof x === "string") {10        return true;11    } else {12        throw new Error("");13    }14  }15}

ただし、次の場合、Flow はエラーを発生させます。

1function numOrStrError(x: mixed): x is number | string {2  return (typeof x === "number" || typeof x === "boolean");
3}
2:36-2:57: Cannot return `((typeof x) === "number") || ((typeof x) === "boolean")` because in the type inferred for type guard parameter `x` [1]: [incompatible-return] Either boolean [2] is incompatible with number [3]. Or boolean [2] is incompatible with string [4].
  1. 絞り込まれるパラメータは、型ガード関数の本体で再割り当てすることはできません。したがって、次はエラーになります。
1function isNumberError1(x: mixed): x is number {2  x = 1;3  return typeof x === "number";
4}
3:10-3:30: Cannot use type guard parameter `x` [1] because at this return point it is writen to in [2]. [function-predicate]
1function isNumberError2(x: mixed): x is number {
2 function foo() {3 x = 1;4 }5 foo();6 return typeof x === "number";7}
1:36-1:36: Cannot use type guard parameter `x`, because `x` [1] is reassigned in [2]. [function-predicate]

採用

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

  • flow および flow-parser: 0.209.1。v0.209.1 から v0.211.1 の間では、.flowconfig の [options] ヘッダーの下で、type_guards=true を追加することで明示的に有効にする必要があります。
  • prettier: 3
  • babel-plugin-syntax-hermes-parser を使用した babel。セットアップ手順については、Babel ガイド を参照してください。
  • hermes-eslint を使用した eslint。セットアップ手順については、ESLint ガイド を参照してください。