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