常見問題
我檢查過 foo.bar
並非 null
,但 Flow 仍認為是。為什麼會發生這種情況,我該如何修正?
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}
我處於封閉中,而 Flow 忽略了聲明 foo.bar
已定義的 if 檢查。為什麼?
在上一節中,我們展示了在函式呼叫後如何遺失變更。在封閉中會發生完全相同的情況,因為 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]
此處的解決方案是將 if 檢查移至 forEach
中,或將 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 http://flow.dev.org.tw/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 http://flow.dev.org.tw/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);
為什麼我無法精煉物件的聯集?
有兩個潛在原因
- 您正在使用不精確的物件。
- 您正在解構物件。在解構時,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。
在其他情況下也會發生這種情況,而且可能較難理解。您會收到類似於 缺少 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>
對應於所謂的 泛型,用於表達傳遞至 map 的函式類型與陣列類型相關聯的事實。
了解泛型的邏輯可能很有用,但您真正需要知道的是,讓您的類型化有效,您需要協助 Flow 了解 genericArray
的類型。
您可以透過註解已匯出的常數來執行此操作
1const array = ['a', 'b']2export const genericArray: Array<string> = array.map(a => a)