跳至主要内容

数组

数组类型表示长度未知的列表,其中所有项都具有相同类型。这与元组类型形成对比,元组类型具有固定长度,并且每个元素可以具有不同的类型。

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]