跳至主要內容

陣列

陣列類型代表長度未知的清單,其中所有項目具有相同的類型。這與元組類型不同,後者具有固定長度,且每個元素可以具有不同的類型。

JavaScript 陣列文字值可用於建立元組和陣列類型

1const arr: Array<number> = [1, 2, 3]; // As an array type2const tup: [number, number, number] = [1, 2, 3]; // As a tuple type

Array 類型

類型 Array<T> 代表類型為 T 的項目陣列。例如,數字陣列將為 Array<number>

1const arr: Array<number> = [1, 2, 3];

您可以在 Array<T> 中放置任何類型

1const arr1: Array<boolean> = [true, false, true];2const arr2: Array<string> = ["A", "B", "C"];3const arr3: Array<mixed> = [1, true, "three"];

$ReadOnlyArray 類型

您可以使用類型 $ReadOnlyArray<T> 取代 Array<T> 來表示無法變異的唯讀陣列。您無法直接寫入唯讀陣列,也無法使用會變異陣列的方法,例如 .push().unshift() 等。

1const readonlyArray: $ReadOnlyArray<number> = [1, 2, 3]2
3const first = readonlyArray[0]; // OK to read4readonlyArray[1] = 20;          // Error!
5readonlyArray.push(4); // Error!
6readonlyArray.unshift(4); // Error!
4:1-4:16: Cannot assign `20` to `readonlyArray[1]` because read-only arrays cannot be written to. [cannot-write]
5:15-5:18: Cannot call `readonlyArray.push` because property `push` is missing in `$ReadOnlyArray` [1]. [prop-missing]
6:15-6:21: Cannot call `readonlyArray.unshift` because property `unshift` is missing in `$ReadOnlyArray` [1]. [prop-missing]

請注意,類型為 $ReadOnlyArray<T> 的陣列仍然可以有可變的元素

1const readonlyArray: $ReadOnlyArray<{x: number}> = [{x: 1}];2readonlyArray[0] = {x: 42}; // Error!
3readonlyArray[0].x = 42; // Works
2:1-2:16: Cannot assign object literal to `readonlyArray[0]` because read-only arrays cannot be written to. [cannot-write]

使用 $ReadOnlyArray 取代 Array 的主要優點是,$ReadOnlyArray 的類型參數是協變,而 Array 的類型參數是不變。這表示 $ReadOnlyArray<number>$ReadOnlyArray<number | string> 的子類型,而 Array<number> 並非 Array<number | string> 的子類型。因此,在各種類型元素的陣列類型註解中使用 $ReadOnlyArray 通常很有用。例如,以下場景

1const someOperation = (arr: Array<number | string>) => {2  // Here we could do `arr.push('a string')`3}4
5const array: Array<number> = [1];6someOperation(array) // Error!
6:15-6:19: Cannot call `someOperation` with `array` bound to `arr` because string [1] is incompatible with number [2] in array element. Arrays are invariantly typed. See https://flow.dev.org.tw/en/docs/faq/#why-cant-i-pass-an-arraystring-to-a-function-that-takes-an-arraystring-number. [incompatible-call]

由於 someOperation 函式的參數 arr 被設定為可變的 Array,因此在該範圍內將字串推入其中是可行的,這將會破壞外部 array 變數的類型合約。在此情況下,透過將參數註解為 $ReadOnlyArray,Flow 可以確定不會發生這種情況,且不會發生錯誤

1const someOperation = (arr: $ReadOnlyArray<number | string>) => {2  // Nothing can be added to `arr`3}4
5const array: Array<number> = [1];6someOperation(array); // Works

$ReadOnlyArray<mixed> 表示所有陣列和所有元組的超類型

1const tup: [number, string] = [1, 'hi'];2const arr: Array<number> = [1, 2];3
4function f(xs: $ReadOnlyArray<mixed>) { /* ... */ }5
6f(tup); // Works7f(arr); // Works

空陣列文字

在 Flow 中,空陣列文字 ([]) 在其註解需求方面有特別的處理方式。它們的特殊之處在於,它們不包含足夠的資訊來確定其類型,同時它們又太過常見,無法在它們的直接內容中總是需要類型註解。

因此,Flow 遵循以下規則來設定空陣列的類型

內容推論

首先,如果存在內容資訊,我們將使用它來確定陣列元素類型

1function takesStringArray(x: Array<string>): void {}2
3const arr1: Array<string> = [];4takesStringArray([]);

在兩種情況下,[] 都會被設定為 Array<string>

請注意,要讓內容資訊發揮作用,類型必須在陣列定義時立即可用。這表示以下程式碼中的最後兩行會發生錯誤

1function takesStringArray(x: Array<string>): void {}2
3const arr2 = [];
4takesStringArray(arr2);
3:7-3:10: Cannot determine type of empty array literal. Please provide an annotation. [missing-empty-array-annot]
4:18-4:21: Cannot call `takesStringArray` with `arr2` bound to `x` because string [1] is incompatible with unknown element of empty array [2] in array element. Arrays are invariantly typed. See https://flow.dev.org.tw/en/docs/faq/#why-cant-i-pass-an-arraystring-to-a-function-that-takes-an-arraystring-number. [incompatible-call]

第二個錯誤是因為 arr2 被推論為 Array<empty>,這會導致在呼叫 takesStringArray 時產生另一個錯誤。

初始化推論

Flow 提供另一種方法來判斷空陣列的類型,當它們立即指定給變數時

const arr3 = [];

它這樣做的方式讓人想起 未初始化變數 的類型化:Flow 嘗試選擇變數的「第一個」指定指定來定義其類型。對於空陣列,指定可以是

  • 索引寫入陳述式 a[i] = e;,或
  • 陣列 push 呼叫 a.push(e)

在任何一種情況下,e 的類型都用作陣列元素的類型。

以下是一些範例

直線程式碼

找到第一個指定後,陣列元素的類型會固定為指定表達式的類型。隨後寫入具有不同類型元素的陣列會產生錯誤

1const arr3 = [];2arr3.push(42); // arr3 is inferred as Array<number>3arr3.push("abc"); // Error!
3:11-3:15: Cannot call `arr3.push` because string [1] is incompatible with number [2] in array element. [incompatible-call]

條件式程式碼

如果陣列指定在條件式陳述式的同層分支中,陣列元素的類型會固定為指定類型的聯集

1declare const cond: boolean;2
3const arr4 = [];4if (cond) {5  arr4[0] = 42;6} else {7  arr4.push("abc");8}9// arr4 is inferred as Array<number | string>10arr4.push("def"); // Works11arr4[0] = true; // Error!
11:11-11:14: Cannot assign `true` to `arr4[0]` because: [incompatible-type] Either boolean [1] is incompatible with number [2]. Or boolean [1] is incompatible with string [3].

較近的範圍優先

當指定發生在多個範圍時,較淺的指定範圍優先

1const arr5 = [];2function f() {3  arr5.push(42); // Error!
4}5f();6arr5.push("abc"); // This assignment wins. arr5 is inferred as Array<string>7arr5.push(1); // Error!
3:13-3:14: Cannot call `arr5.push` because number [1] is incompatible with string [2] in array element. [incompatible-call]
7:11-7:11: Cannot call `arr5.push` because number [1] is incompatible with string [2] in array element. [incompatible-call]

陣列存取不安全

當您從陣列中擷取元素時,它總是可能為 undefined。您可能存取了超出陣列邊界的索引,或者元素可能不存在,因為它是一個「稀疏陣列」。

例如,您可能存取了超出陣列邊界的元素

1const array: Array<number> = [0, 1, 2];2const value: number = array[3]; // Works3                         // ^ undefined

或者,如果它是「稀疏陣列」,您可能存取了不存在的元素

1const array: Array<number> = [];2
3array[0] = 0;4array[2] = 2;5
6const value: number = array[1]; // Works7                         // ^ undefined

為了確保安全,Flow 必須將每個陣列存取標示為「可能未定義」。

Flow 沒有這樣做,因為這樣會非常不方便使用。您將被迫精煉存取陣列時取得的每個值的類型。

1const array: Array<number> = [0, 1, 2];2const value: number | void = array[1];3
4if (value !== undefined) {5  // number6}

不建議使用的陣列類型簡寫語法

Array<T> 語法有一個替代方案:T[]。此語法不建議使用,並可能在未來被棄用。

1const arr: number[] = [0, 1, 2, 3];

請注意,?Type[] 等同於 ?Array<T>,而不是 Array<?T>

1const arr1: ?number[] = null;   // Works2const arr2: ?number[] = [1, 2]; // Works3const arr3: ?number[] = [null]; // Error!
3:26-3:29: Cannot assign array literal to `arr3` because null [1] is incompatible with number [2] in array element. [incompatible-type]

如果您想讓它成為 Array<?T>,您可以使用括號,例如:(?Type)[]

1const arr1: (?number)[] = null;   // Error!
2const arr2: (?number)[] = [1, 2]; // Works3const arr3: (?number)[] = [null]; // Works
1:27-1:30: Cannot assign `null` to `arr1` because null [1] is incompatible with array type [2]. [incompatible-type]