数组
数组类型表示长度未知的列表,其中所有项都具有相同类型。这与元组类型形成对比,元组类型具有固定长度,并且每个元素可以具有不同的类型。
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.node.org.cn/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.node.org.cn/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 将不得不将每个数组访问标记为“可能为 undefined”。
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]