数组
1766字约6分钟
TypeScript
2023-01-30
✨数组类型
JavaScript 数组在 TypeScript 里面分成两种类型,分别是数组(array)和元组(tuple)。
✨数组(array)
数组(array) 有一个根本特征:所有成员的类型必须相同
,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。
数组的类型有两种写法:
- 方式一:
number[]
let arr: number[] = [1, 2, 3];
let arr: (number | string)[];
let arr: any[];
- 方式二:泛型写法
Array<number>
let arr: Array<number> = [1, 2, 3];
let arr: Array<number | string>;
let arr: Array<any>;
注意
数组由于成员数量可以动态变化,所以 TypeScript 不会对数组边界进行检查,越界访问数组并不会报错。
let arr: number[] = [1, 2, 3];
let foo = arr[3]; // 正确
由于数组成员的索引类型都是number,所以读取成员类型也可以写成下面这样。
type Names = string[];
// 第一个数值索引的成员类型
type FirstName = Names[0]; // string
// 所有数值索引的成员类型
type Name = Names[number]; // string
// 读取数组长度
Names["length"];
✨数组的类型推断
如果数组变量没有声明类型,TypeScript 就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。
如果数组初始值是空数组,TypeScript 会推断数组类型 any[]
,后面,为这个数组赋值时,TypeScript 会自动更新类型推断。
但是,类型推断的自动更新只发生初始值为空数组的情况。如果初始值不是空数组,类型推断就不会更新。
const arrIsNull = [];
arrIsNull; // 推断为 any[]
arrIsNull.push(123);
arrIsNull; // 推断类型为 number[]
arrIsNull.push("abc");
arrIsNull; // 推断类型为 (string|number)[]
// 推断类型为 number[]
const arr = [123];
arr.push("abc"); // 报错
✨只读数组,const 断言
TypeScript 允许声明只读数组,方法是在数组类型前面加上 readonly
关键字。
const arr: readonly number[] = [0, 1];
arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错
只读数组还有一种声明方法,就是使用“const 断言”
:
const arr = [0, 1] as const;
arr[0] = [2]; // 报错
上面示例中,as const
告诉 TypeScript,推断类型时要把变量arr推断为只读数组,从而使得数组成员无法改变。
TypeScript 将readonly number[]
与number[]
视为两种不一样的类型,后者是前者的子类型。子类型继承了父类型的所有特征,并加上了自己的特征,所以子类型number[]
可以用于所有使用父类型的场合,反过来就不行。
// 例1
let a1: number[] = [0, 1];
let a2: readonly number[] = a1; // 正确
a1 = a2; // 报错
// 例2
function getSum(s: number[]) {
// ...
}
const arr: readonly number[] = [1, 2, 3];
getSum(arr); // 报错
解决上面问题的方法是使用类型断言
,如:getSum(arr as number[])
,详见《类型断言》一章。
注意
注意,readonly
关键字不能与数组的泛型写法一起使用。
// 报错
const arr: readonly Array<number> = [0, 1];
TypeScript 提供了两个专门的泛型,用来生成只读数组的类型:
const a1: ReadonlyArray<number> = [0, 1];
const a2: Readonly<number[]> = [0, 1];
✨多维数组
TypeScript 使用T[][]
的形式,表示二维数组,T
是最底层数组成员的类型。
var multi: number[][] = [
[1, 2, 3],
[23, 24, 25],
];
✨元组类型(tuple)
元组(tuple)是 TypeScript 特有的数据类型,JavaScript 没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。
元组必须明确声明每个成员的类型。
const s: [string, string, boolean] = ["a", "b", true];
元组成员的类型可以添加问号后缀(?),表示该成员是可选的。
注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。
let a: [number, number?] = [1];
由于需要声明每个成员的类型,所以大多数情况下,元组的成员数量是有限的,从类型声明就可以明确知道,元组包含多少个成员,越界的成员会报错。
let x: [string, string] = ["a", "b"];
x[2] = "c"; // 报错
但是,使用扩展运算符(...),可以表示不限成员数量的元组。
扩展运算符用在元组的任意位置都可以,但是它后面只能是数组或元组。
type NamedNums = [string, ...number[]];
const a: NamedNums = ["A", 1, 2];
const b: NamedNums = ["B", 1, 2, 3];
// 任意位置
type t1 = [string, number, ...boolean[]];
type t2 = [string, ...boolean[], number];
type t3 = [...boolean[], string, number];
元组可以通过方括号,读取成员类型。
// 单个
type Tuple = [string, number];
type Age = Tuple[1]; // number
// 所有数值索引的成员类型,由于元组类型是不同的,所以返回的类型是联合类型
type Tuple = [string, number, Date];
type TupleEl = Tuple[number]; // string|number|Date
✨只读元组
元组也可以是只读的,不允许修改,有两种写法:
// 写法一
type t = readonly [number, string];
// 写法二 泛型写法
type t = Readonly<[number, string]>;
跟数组一样,只读元组是元组的父类型。所以,元组可以替代只读元组,而只读元组不能替代元组。
type t1 = readonly [number, number];
type t2 = [number, number];
let x: t2 = [1, 2];
let y: t1 = x; // 正确
x = y; // 报错
✨成员数量的推断
如果没有扩展运算符,TypeScript 会推断出元组的成员数量(即元组长度)。
// 下面示例会报错,原因是 TypeScript 发现元组point的长度是2,不可能等于3,这个判断无意义。
function f(point: [number, number]) {
if (point.length === 3) {
// 报错
// ...
}
}
// 下面示例会报错,原因是 TypeScript 发现point.length的类型是1|2|3,不可能等于4。
function f(point: [number, number?, number?]) {
if (point.length === 4) {
// 报错
// ...
}
}
如果使用了扩展运算符,TypeScript 就无法推断出成员数量。
一旦扩展运算符使得元组的成员数量无法推断,TypeScript 内部就会把该元组当成数组处理。
const myTuple: [...string[]] = ["a", "b", "c"];
if (myTuple.length === 4) {
// 正确
// ...
}
✨扩展运算符与成员数量
扩展运算符(...
)将数组(注意,不是元组)转换成一个逗号分隔的序列,这时 TypeScript 会认为这个序列的成员数量是不确定的,因为数组的成员数量是不确定的。
这导致如果函数调用时,使用扩展运算符传入函数参数,可能发生参数数量与数组长度不匹配的报错。
const arr = [1, 2];
function add(x: number, y: number) {
// ...
}
add(...arr); // 报错 A spread argument must either have a tuple type or be passed to a rest parameter.(2556)
解决这个问题的一个方法,就是把成员数量不确定的数组,写成成员数量确定的元组,再使用扩展运算符。
const arr: [number, number] = [1, 2];
function add(x: number, y: number) {
// ...
}
add(...arr); // 正确
另一种写法是使用as const
断言。
const arr = [1, 2] as const;