数组与元组
数组与元组
数组
TypeScript数组有一个根本特征: 所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。
let arr: number[] = [1, 2, 3];
let arr1: Array<number> = [1, 2, 3];如果数组成员的类型比较复杂,可以写在圆括号里面。
let arr: (number | string)[];
let arr1: Array<number | string>;这个例子里面的圆括号是必须的,否则因为竖杠|的优先级低于[],
TypeScript 允许使用方括号读取数组成员的类型。
type Names = string[];
type Name = Names[0]; // string由于数组成员的索引类型都是number,所以读取成员类型也可以写成下面这样。
type Names = string[];
type Name = Names[number]; // string数组的类型推断
如果数组变量没有声明类型,TypeScript 就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。
如果变量的初始值是空数组,那么 TypeScript 会推断数组类型是any[]。
const arr = [];
arr // 推断为 any[]
arr.push(123);
arr // 推断类型为 number[]
arr.push('abc');
arr // 推断类型为 (string|number)[]类型推断的自动更新只发生初始值为空数组的情况。如果初始值不是空数组,类型推断就不会更新。
// 推断类型为 number[]
const arr = [123];
arr.push('abc'); // 报错只读数组,const 断言
上面示例中,修改const命令声明的数组的成员是允许的。
但是,很多时候确实有声明为只读数组的需求,即不允许变动数组成员。
TypeScript 允许声明只读数组,方法是在数组类型前面加上readonly关键字。
提示
只读数组没有pop()、push()之类会改变原数组的方法
const arr: readonly number[] = [0, 1];
arr[1] = 2; // 报错
arr.push(3); // 报错delete arr[0]; // 报错子类型继承了父类型的所有特征,并加上了自己的特征,所以子类型number[]可以用于所有使用父类型的场合,反过来就不行。
let a1: number[] = [0, 1];
let a2: readonly number[] = a1; // 正确
a1 = a2; // 报错子类型number[]可以赋值给父类型readonly number[],但是反过来就会报错。
function getSum(s: number[]) {// ...
}
const arr: readonly number[] = [1, 2, 3];
getSum(arr) // 报错 相当于 只读数组赋值给可变数组 所以报错只读写法
const a1: ReadonlyArray<number> = [0, 1];
const a2: Readonly<number[]> = [0, 1];
const arr = [0, 1] as const;
arr[0] = [2]; // 报错多维数组
TypeScript 使用T[][]的形式,表示二维数组,T是最底层数组成员的类型。
var multi: number[][] = [[1, 2, 3], [23, 24, 25]];元组
元组(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 = [...any[]];元组的成员可以添加成员名,这个成员名是说明性的,可以任意取名,没有实际作用。
type Color = [red: number, green: number, blue: number];
const c: Color = [255, 255, 255];元组可以通过方括号,读取成员类型。
type Tuple = [string, number];
type Age = Tuple[1]; // number只读元组
元组也可以是只读的,不允许修改,有两种写法。
// 写法一
type t = readonly [number, string]
// 写法二
type t = Readonly<[number, string]>成员数量的推断
如果没有可选成员和扩展运算符,TypeScript 会推断出元组的成员数量(即元组长度)。
function f(point: [number, number]) {
if (point.length === 3) { // 报错// ...
}
}扩展运算符与成员数量
扩展运算符(...)将数组(注意,不是元组)转换成一个逗号分隔的序列,这时 TypeScript 会认为这个序列的成员数量是不确定的,因为数组的成员数量是不确定的。
这导致如果函数调用时,使用扩展运算符传入函数参数,可能发生参数数量与数组长度不匹配的报错。
const arr = [1, 2];
function add(x: number, y: number) {
// ...
}
add(...arr) // 报错上面示例会报错,原因是函数add()只能接受两个参数,但是传入的是...arr,TypeScript 认为转换后的参数个数是不确定的。
const arr: [number, number] = [1, 2];
function add(x: number, y: number) {// ...
}
add(...arr) // 正确一种写法是使用as const断言。
const arr = [1, 2] as const;上面这种写法也可以,因为 TypeScript 会认为arr的类型是readonly [1, 2],这是一个只读的值类型,可以当作数组,也可以当作元组。