FAQ
foo.bar
がnull
ではないことを確認しましたが、Flowはまだnull
の可能性があると認識しています。なぜこのようなことが起こり、どのように修正できますか?
Flowは副作用を追跡しないため、関数呼び出しによってチェックが無効になる可能性があります。これはリファインメントの無効化と呼ばれます。例
1type Param = {2 bar: ?string,3}4function myFunc(foo: Param): string {5 if (foo.bar) {6 console.log("checked!");7 return foo.bar; // Flow errors. If you remove the console.log, it works 8 }9
10 return "default string";11}
7:12-7:18: Cannot return `foo.bar` because null or undefined [1] is incompatible with string [2]. [incompatible-return]
チェック済みの値をローカル変数に格納することで、この問題を回避できます。
1type Param = {2 bar: ?string,3}4function myFunc(foo: Param): string {5 if (foo.bar) {6 const bar = foo.bar;7 console.log("checked!");8 return bar; // Ok!9 }10
11 return "default string";12}
私の値がA型であることを確認しましたが、なぜFlowはまだA | Bだと考えているのですか?
リファインメントの無効化は、変数が更新された場合にも発生する可能性があります。
1type T = string | number;2
3let x: T = 'hi';4
5function f() {6 x = 1;7}8
9if (typeof x === 'string') {10 f();11 x as string; 12}
11:3-11:3: Cannot cast `x` to string because number [1] is incompatible with string [2]. [incompatible-cast]
回避策としては、変数をconst
にし、再代入を避けるようにコードをリファクタリングすることです。
1type T = string | number;2
3const x: T = 'hi';4
5function f(x: T): number {6 return 1;7}8
9if (typeof x === 'string') {10 const xUpdated = f(x);11 xUpdated as number;12 x as string;13}
クロージャ内にいて、foo.bar
が定義されていることを確認するifチェックをFlowが無視しています。なぜですか?
前のセクションでは、関数呼び出しの後、リファインメントが失われる方法を示しました。Flowは値がクロージャが呼び出される前にどのように変化する可能性があるかを追跡しないため、クロージャ内でもまったく同じことが起こります。
1const people = [{age: 12}, {age: 18}, {age: 24}];2const oldPerson: {age: ?number} = {age: 70};3if (oldPerson.age) {4 people.forEach(person => {5 console.log(`The person is ${person.age} and the old one is ${oldPerson.age}`); 6 })7}
5:67-5:79: Cannot coerce `oldPerson.age` to string because null or undefined [1] should not be coerced. [incompatible-type]
ここでは、`forEach`内でifチェックを移動するか、`age`を中間変数に代入することが解決策です。
1const people = [{age: 12}, {age: 18}, {age: 24}];2const oldPerson: {age: ?number} = {age: 70};3if (oldPerson.age) {4 const age = oldPerson.age;5 people.forEach(person => {6 console.log(`The person is ${person.age} and the old one is ${age}`);7 })8}
しかし、Flowはこの関数がこのリファインメントを無効にできないことを理解するはずです、よね?
Flowは完全ではありませんそのため、すべてのコードを完全にチェックすることはできません。代わりに、Flowは健全性を保つために保守的な仮定を行います。
if句で関数を使用してプロパティの型をチェックできないのはなぜですか?
Flowは別々の関数呼び出しで行われたリファインメントを追跡しません。
1const add = (first: number, second: number) => first + second;2const val: string | number = 1;3const isNumber = (x: mixed): boolean => typeof x === 'number';4if (isNumber(val)) {5 add(val, 2); 6}
5:7-5:9: Cannot call `add` with `val` bound to `first` because string [1] is incompatible with number [2]. [incompatible-call]
ただし、関数に型ガードを付けることで、この動作を実現できます。
1const add = (first: number, second: number) => first + second;2const val: string | number = 1;3// Return annotation updated:4const isNumber = (x: mixed): x is number => typeof x === 'number';5if (isNumber(val)) {6 add(val, 2);7}
Array<string>
をArray<string | number>
を受け取る関数に渡せないのはなぜですか?
関数の引数は配列に`string`値を許可していますが、この場合、Flowは元の配列が`number`を受け取るのを防ぎます。関数内では、`number`を引数の配列に追加できるため、元の配列の型が正確ではなくなります。
引数の型を$ReadOnlyArray<string | number>
に変更して共変にすることで、このエラーを修正できます。これにより、関数本体が配列に何もプッシュできなくなり、より狭い型を受け入れることができます。
例として、これは機能しません。
1const fn = (arr: Array<string | number>) => {2 arr.push(123); // Oops! Array<string> was passed in - now inaccurate3 return arr;4};5
6const arr: Array<string> = ['abc'];7
8fn(arr); // Error!
8:4-8:6: Cannot call `fn` with `arr` bound to `arr` because number [1] is incompatible with string [2] in array element. Arrays are invariantly typed. See https://flow.dokyumento.jp/en/docs/faq/#why-cant-i-pass-an-arraystring-to-a-function-that-takes-an-arraystring-number. [incompatible-call]
しかし、$ReadOnlyArray
を使用すると、目的の動作を実現できます。
1const fn = (arr: $ReadOnlyArray<string | number>) => {2 // arr.push(321); NOTE! Since you are using $ReadOnlyArray<...> you cannot push anything to it3 return arr;4};5
6const arr: Array<string> = ['abc'];7
8fn(arr);
{a: string}
を{a: string | number}
を受け取る関数に渡せないのはなぜですか?
関数の引数はフィールドに`string`値を許可していますが、この場合、Flowは元のオブジェクトに`number`が書き込まれるのを防ぎます。関数本体内では、プロパティ`a`に`number`が代入されるようにオブジェクトを変化させることができるため、元のオブジェクトの型が正確ではなくなります。
プロパティを共変(読み取り専用)にすることで、このエラーを修正できます:{+a: string | number}
。これにより、関数本体がプロパティに書き込むことができなくなり、より制限された型を関数に安全に渡すことができます。
例として、これは機能しません。
1const fn = (obj: {a: string | number}) => {2 obj.a = 123; // Oops! {a: string} was passed in - now inaccurate3 return obj;4};5
6const object: {a: string} = {a: 'str' };7
8fn(object); // Error!
8:4-8:9: Cannot call `fn` with `object` bound to `obj` because number [1] is incompatible with string [2] in property `a`. This property is invariantly typed. See https://flow.dokyumento.jp/en/docs/faq/#why-cant-i-pass-a-string-to-a-function-that-takes-a-string-number. [incompatible-call]
しかし、共変プロパティを使用すると、目的の動作を実現できます。
1const fn = (obj: {+a: string | number}) => {2 // obj.a = 123 NOTE! Since you are using covariant {+a: string | number}, you can't mutate it3 return obj;4};5
6const object: {a: string} = { a: 'str' };7
8fn(object);
オブジェクトのユニオンをリファインできないのはなぜですか?
考えられる理由は2つあります。
- 不正確なオブジェクトを使用しています。
- オブジェクトをデストラクチャリングしています。デストラクチャリングすると、Flowはオブジェクトのプロパティの追跡を失います。
動作しない例
1type Action =2 | {type: 'A', payload: string}3 | {type: 'B', payload: number};4
5// Not OK6const fn = ({type, payload}: Action) => {7 switch (type) {8 case 'A': return payload.length; // Error! 9 case 'B': return payload + 10;10 }11}
8:30-8:35: Cannot get `payload.length` because property `length` is missing in `Number` [1]. [prop-missing]
修正された例
1type Action =2 | {type: 'A', payload: string}3 | {type: 'B', payload: number};4
5// OK6const fn = (action: Action) => {7 switch (action.type) {8 case 'A': return action.payload.length;9 case 'B': return action.payload + 10;10 }11}
「型アノテーションがありません」というエラーが発生しました。どこから来たエラーですか?
Flowは、スケーラビリティを確保するために、モジュール境界で型アノテーションを必要とします。詳細については、この件に関するブログ投稿をご覧ください。
最も頻繁に見られるケースは、関数またはReactコンポーネントをエクスポートする場合です。Flowは入力のアノテーションを必要とします。たとえば、この例では、Flowはエラーを報告します。
1export const add = a => a + 1;
1:20-1:20: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type annotation at identifier: [signature-verification-failure]1:20-1:20: Missing an annotation on `a`. [missing-local-annot]1:21-1:20: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type annotation at function return: [signature-verification-failure]
ここでは、`add`のパラメータに型を追加することで修正できます。
1export const add = (a: number): number => a + 1;
エクスポートされたReactコンポーネントにアノテーションを付ける方法については、HOCに関するドキュメントをご覧ください。
他にもこのようなケースがあり、理解するのが難しい場合があります。`Missing type annotation for U`のようなエラーが表示されます。たとえば、次のコードを書いたとします。
1const array = ['a', 'b']2export const genericArray = array.map(a => a)
2:29-2:45: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. [signature-verification-failure]
ここでは、Flowは`export`でエラーを報告し、型アノテーションを要求します。Flowは、ジェネリック関数によって返されるエクスポートのアノテーションを必要としています。`Array.prototype.map`の型は`map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U>`です。`<U>`は、マップに渡される関数の型が配列の型にリンクされていることを表すジェネリックと呼ばれるものです。
ジェネリックの背後にあるロジックを理解することは役立つかもしれませんが、型を有効にするために本当に知る必要があるのは、Flowが`genericArray`の型を理解するのを助ける必要があるということです。
エクスポートされた定数にアノテーションを付けることで、これを行うことができます。
1const array = ['a', 'b']2export const genericArray: Array<string> = array.map(a => a)