跳至主要內容

常見問題

我檢查過 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);

為什麼我無法精煉物件的聯集?

有兩個潛在原因

  1. 您正在使用不精確的物件。
  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

在其他情況下也會發生這種情況,而且可能較難理解。您會收到類似於 缺少 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)