型リファインメント
リファインメントを使用すると、条件付きテストに基づいて値の型を絞り込むことができます。
たとえば、以下の関数では、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
)チェックは、value
がnull
とvoid
であるかを正確にチェックします。これは、null
とvoid
のユニオンを作成する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の条件文では、ブール値でないものを使用できます。0
、NaN
、""
、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}