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

型リファインメント

リファインメントを使用すると、条件付きテストに基づいて値の型を絞り込むことができます。

たとえば、以下の関数では、value"A"または"B"ユニオンです。

1function func(value: "A" | "B") {2  if (value === "A") {3    value as "A";4  }5}

ifブロック内では、if文が真の場合にのみそうなるため、valueは"A"である必要があります。

静的型チェッカーがif文内の値が"A"であると判断できる機能は、リファインメントと呼ばれます。

次に、if文にelseブロックを追加します。

1function func(value: "A" | "B") {2  if (value === "A") {3    value as "A";4  } else {5    value as "B";6  }7}

elseブロック内では、valueは"B"である必要があります。これは、"A"または"B"のいずれかであり、可能性から"A"を削除したためです。

Flowでのリファイン方法

typeofチェック

typeof value === "<type>"チェックを使用して、typeof演算子でサポートされているカテゴリのいずれかに値をリファインできます。

typeof演算子は、"undefined""boolean""number""bigint""string""symbol""function"、または"object"を出力できます。

typeof演算子はオブジェクトに対して"object"を返しますが、nullと配列も同様に返すことに注意してください。

1function func(value: mixed) {2  if (typeof value === "string") {3    value as string;4  } else if (typeof value === "boolean") {5    value as boolean;6  } else if (typeof value === "object") {7    // `value` could be null, an array, or an object8    value as null | interface {} | $ReadOnlyArray<mixed>;9  }10}

nullをチェックするには、value === null等価性チェックを使用します。

1function func(value: mixed) {2  if (value === null) {3    value as null; // `value` is null4  }5}

配列をチェックするには、Array.isArrayを使用します。

1function func(value: mixed) {2  if (Array.isArray(value)) {3    value as $ReadOnlyArray<mixed>; // `value` is an array4  }5}

等価性チェック

導入例で示したように、等価性チェックを使用して、値を特定の型に絞り込むことができます。これは、switchステートメントで行われた等価性チェックにも当てはまります。

1function func(value: "A" | "B" | "C") {2  if (value === "A") {3    value as "A";4  } else {5    value as "B" | "C";6  }7
8  switch (value) {9    case "A":10      value as "A";11      break;12    case "B":13      value as "B";14      break;15    case "C":16      value as "C";17      break;18  }19}

一般的にはJavaScriptで==を使用することは推奨されていませんが、強制型変換を行うため、value == null(またはvalue != null)チェックは、valuenullvoidであるかを正確にチェックします。これは、nullvoidのユニオンを作成するFlowのmaybe型とうまく連携します。

1function func(value: ?string) {2  if (value != null) {3    value as string;4  } else {5    value as null | void;6  }7}

非連結オブジェクトユニオンと呼ぶ共通のタグに基づいて、オブジェクト型のユニオンをリファインできます。

1type A = {type: "A", s: string};2type B = {type: "B", n: number};3
4function func(value: A | B) {5  if (value.type === "A") {6    // `value` is A7    value.s as string; // Works8  } else {9    // `value` is B10    value.n as number; // Works11  }12}

真偽値チェック

JavaScriptの条件文では、ブール値でないものを使用できます。0NaN""null、およびundefinedはすべてfalseに強制変換され(したがって「偽」と見なされます)。他の値はtrueに強制変換されます(したがって「真」と見なされます)。

1function func(value: ?string) {2  if (value) {3    value as string; // Works4  } else {5    value as null | void; // Error! Could still be the empty string ""
6 }7}
5:5-5:9: Cannot cast `value` to union type because string [1] is incompatible with literal union [2]. [incompatible-cast]

上の例では、値が文字列または数値になる可能性がある場合に、真偽値チェックを行うことが推奨されない理由がわかります。意図せずに""または0に対してチェックする可能性があります。このシナリオを防ぐために、Flow lintであるsketchy-nullを作成しました。

1// flowlint sketchy-null:error2function func(value: ?string) {3  if (value) { // Error!
4 }5}
3:7-3:11: Sketchy null check on string [1] which is potentially an empty string. Perhaps you meant to check for null or undefined [2]? [sketchy-null-string]

instanceofチェック

instanceof演算子を使用して、値を絞り込むこともできます。これは、指定されたコンストラクターのプロトタイプが値のプロトタイプチェーンのどこかにあるかどうかをチェックします。

1class A {2  amaze(): void {}3}4class B extends A {5  build(): void {}6}7
8function func(value: mixed) {9  if (value instanceof B) {10    value.amaze(); // Works11    value.build(); // Works12  }13
14  if (value instanceof A) {15    value.amaze(); // Works16    value.build(); // Error
17 }18 19 if (value instanceof Object) {20 value.toString(); // Works21 }22}
16:11-16:15: Cannot call `value.build` because property `build` is missing in `A` [1]. [prop-missing]

代入

Flowは制御フローに従い、代入後に変数の型を絞り込みます。

1declare const b: boolean;2
3let x: ?string = b ? "str" : null;4
5x as ?string;6
7x = "hi";8
9// We know `x` must now be a string after the assignment10x as string; // Works

型ガード

型ガードである関数を定義することで、再利用可能なリファインメントを作成できます。

1function nonMaybe<T>(x: ?T): x is T {2  return x != null;3}4
5function func(value: ?string) {6  if (nonMaybe(value)) {7    value as string; // Works!8  }9}

リファインメントの無効化

たとえば、リファインメントを無効にすることもできます。

1function otherFunc() { /* ... */ }2
3function func(value: {prop?: string}) {4  if (value.prop) {5    otherFunc();6    value.prop.charAt(0); // Error!
7 }8}
6:16-6:21: Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]

この理由は、otherFunc()が値に対して何かをしていないとは限らないためです。次のシナリオを想像してください。

1const obj: {prop?: string} = {prop: "test"};2
3function otherFunc() {4  if (Math.random() > 0.5) {5    delete obj.prop;6  }7}8
9function func(value: {prop?: string}) {10  if (value.prop) {11    otherFunc();12    value.prop.charAt(0); // Error!
13 }14}15 16func(obj);
12:16-12:21: Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]

otherFunc()内では、propを削除することがあります。Flowは、if (value.prop)チェックがまだtrueであるかどうかを知らないため、リファインメントを無効にします。

これを回避する簡単な方法があります。別の関数を呼び出す前に値を格納し、代わりに格納された値を使用します。これにより、リファインメントが無効になるのを防ぐことができます。

1function otherFunc() { /* ... */ }2
3function func(value: {prop?: string}) {4  if (value.prop) {5    const prop = value.prop;6    otherFunc();7    prop.charAt(0);8  }9}