# 数组
TypeScript 数组有一个根本特征:所有成员的类型必须相同**,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。数组的项中不允许出现其他的类型。
数组的类型有两种写法:
- 第一种写法是「类型 + 方括号」表示法。在数组成员的类型后面,加上一对方括号。
- 如果数组成员的类型比较复杂,可以写在圆括号里面。
- 如果数组成员可以是任意类型,写成any[]。当然,这种写法是应该避免的。
let arr:number[] = [1, 2, 3];
let arr:(number|string)[]; // 圆括号是必须的,否则因为竖杠|的优先级低于[],会把number|string[]理解成number和string[]的联合类型
let arr:any[];
2
3
- 第二种写法是「数组泛型
Array<elemType>
」。使用TypeScript内置的Array接口。这种写法对于成员类型比较复杂的数组,代码可读性会稍微好一些。
let arr:Array<number> = [1, 2, 3];
let arr:Array<number|string>; // 这种写法本质上属于泛型,这里只要知道怎么写就可以了
2
数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。
数组的成员是可以动态变化的。所以TS不会对数组边界进行检查,越界访问数组并不会报错。
TypeScript 允许使用方括号读取数组成员的类型。
let arr:number[];
arr = [];
arr = [1]; // 数组arr无论有多少个成员,都是正确的------------------------------------
let arr:number[] = [1, 2, 3];
arr[3] = 4;
arr.length = 2;
arr // [1, 2] 数组增加成员或减少成员,都是可以的。------------------------------------
let arr:number[] = [1, 2, 3];
let foo = arr[3]; // 正确 变量foo的值是一个不存在的数组成员,TypeScript并不会报错-----
type Names = string[];
type Name = Names[0]; // string
type Name = Names[number]; // string 数组成员的索引类型都是number,所以读取成员类型也可这样写
2
3
4
5
6
7
8
9
10
11
12
# 数组的类型推断
如果数组变量没有声明类型,TypeScript就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。
- 如果变量的初始值是空数组,那么 TypeScript 会推断数组类型是any[]。
- 后面,为这个数组赋值时,TypeScript 会自动更新类型推断。
const arr = [];
arr // 推断为 any[]
arr.push(123);
arr // 推断类型为 number[]
arr.push('abc');
arr // 推断类型为 (string|number)[]
2
3
4
5
6
- 但是,类型推断的自动更新只发生在初始值为空数组的情况。如果初始值不是空数组,类型推断就不会更新。
const arr = [123]; // 推断类型为 number[] 推断成员类型为number
arr.push('abc'); // 报错 新成员如果不是这个类型,就会报错,而不会更新类型推断
2
# 只读数组,const 断言
JavaScript规定,const命令声明的数组变量是可以改变成员的。
但是,很多时候确实有声明为只读数组的需求,即不允许变动数组成员。TypeScript允许声明只读数组,方法是在数组类型前面加上readonly关键字。
由于只读数组是数组的父类型,所以它不能代替数组。
const arr:readonly number[] = [0, 1];
delete arr[0]; // 报错。arr是一个只读数组,删除、修改、新增数组成员都会报错-----------
// readonly number[]与number[]视为两种不一样的类型,后者是前者的子类型
let a1:number[] = [0, 1];
let a2:readonly number[] = a1; // 正确。子类型number[]可以赋值给父类型readonly number[]
a1 = a2; // 报错。但是反过来就会报错
2
3
4
5
6
注意,readonly关键字不能与数组的泛型写法一起使用。
实际上,TypeScript 提供了两个专门的泛型,用来生成只读数组的类型。
泛型ReadonlyArray<T>
和Readonly<T[]>
都可以用来生成只读数组类型。两者尖括号里面的写法不一样,Readonly<T[]>
的尖括号里面是整个数组(number[]),而ReadonlyArray<T>
的尖括号里面是数组成员(number)。
const arr:readonly Array<number> = [0, 1];// 报错
const a1:ReadonlyArray<number> = [0, 1];
const a2:Readonly<number[]> = [0, 1];
2
3
只读数组还有一种声明方法,就是使用“const 断言”。
const arr = [0, 1] as const; //as const告诉TS,推断类型时要把变量arr推断为只读数组,数组成员无法改变
arr[0] = [2]; // 报错
2
# 多维数组
TypeScript 使用T[][]的形式,表示二维数组,T是最底层数组成员的类型。
var multi:number[][] = [[1,2,3], [23,24,25]]; //表示一个二维数组,最底层数组成员类型是number
# any 在数组中的应用
一个比较常见的做法是,用 any 表示数组中允许出现任意类型。
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
# 元组
元组是TypeScript特有的数据类型,JavaScript没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同**。
由于成员的类型可以不一样,所以元组必须明确声明每个成员的类型。不能省略,否则TypeScript会把一个值自动推断为数组。
const s:[string, string, boolean] = ['a', 'b', true];
// a 的类型被推断为 (number | boolean)[]
let a = [1, true]; //变量a的值其实是一个元组,但是TS会将其推断为一个联合类型的数组
2
3
数组的成员类型写在方括号外面(number[]),元组的成员类型是写在方括号里面([number])。TypeScript 的区分方法就是,成员类型写在方括号里面的就是元组,写在外面的就是数组。
// 数组
let a:number[] = [1];
// 元组
let t:[number] = [1];
2
3
4
元组成员的类型可以添加问号后缀(?),表示该成员是可选的。注意,问号只能用于元组的尾部成员,也就是说,所有可选成员必须在必选成员之后。
let a:[number, number?, string?] = [1];
由于需要声明每个成员的类型,所以大多数情况下,元组的成员数量是有限的,从类型声明就可以明确知道,元组包含多少个成员,越界的成员会报错。
let x:[string, string] = ['a', 'b'];
x[2] = 'c'; // 报错。变量x是一个只有两个成员的元组,如果对第三个成员赋值就报错了
2
越界的元素:当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型。
let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.
2
3
4
5
但是,使用扩展运算符(...),可以表示不限成员数量的元组。扩展运算符(...)用在元组的任意位置都可以,它的后面只能是一个数组或元组。
type t1 = [string, number, ...boolean[]];
type t2 = [string, ...boolean[], number];
type t3 = [...boolean[], string, number];
2
3
如果不确定元组成员的类型和数量,可以写成下面这样。
type Tuple = [...any[]];
元组的成员可以添加成员名,这个成员名是说明性的,可以任意取名,没有实际作用。
type Color = [ //每个成员都有一个名字,没有实际作用,只是用来说明每个成员的含义
red: number,
green: number,
blue: number
];
const c:Color = [255, 255, 255];
2
3
4
5
6
元组可以通过方括号,读取成员类型。
type Tuple = [string, number, Date];
type Age = Tuple[1]; // number 返回1号位置的成员类型
//由于元组的成员都是数值索引,即索引类型都是number,所以可以像下面这样读取
type TupleEl = Tuple[number]; // string|number|Date 表示元组的所有数值索引的成员类型
2
3
4
当赋值或访问一个已知索引的元素时,会得到正确的类型。 可以只赋值其中一项,但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
# 只读元组
元组也可以是只读的,不允许修改,有两种写法。
// 写法一
type t = readonly [number, string]
// 写法二 一个泛型,用到了工具类型Readonly<T>
type t = Readonly<[number, string]>
2
3
4
只读元组是元组的父类型。元组可以替代只读元组,而只读元组不能替代元组。
# 成员数量的推断
如果没有可选成员和扩展运算符,TypeScript 会推断出元组的成员数量(即元组长度)。
如果包含了可选成员,TypeScript会推断出可能的成员数量。
如果使用了扩展运算符,TypeScript 就无法推断出成员数量。此时,TypeScript内部就会把该元组当成数组处理。
function f(
point:[number, number?, number?]
) {
if (point.length === 4) { // 报错
// ...
}
} // TypeScript发现point.length的类型是1|2|3,不可能等于4--------------------------
const myTuple:[...string[]]
= ['a', 'b', 'c'];
if (myTuple.length === 4) { // 正确
// ...
} // 用到了扩展运算符,TypeScript 把myTuple当成数组看待,而数组的成员数量是不确定的-----
2
3
4
5
6
7
8
9
10
11
12
13
# 扩展运算符与成员数量
扩展运算符(...)将数组(注意,不是元组)转换成一个逗号分隔的序列,这时TypeScript会认为这个序列的成员数量是不确定的,因为数组的成员数量是不确定的。
导致如果函数调用时,使用扩展运算符传入函数参数,可能发生参数数量与数组长度不匹配的报错。
const arr = [1, 2];
function add(x:number, y:number){ //函数add()只能接受两个参数
// ...
}
add(...arr) // 报错。传入的是...arr,TypeScript认为转换后的参数个数是不确定的
2
3
4
5
解决这个问题的一个方法,就是把成员数量不确定的数组,写成成员数量确定的元组,再使用扩展运算符。
const arr:[number, number] = [1, 2]; //arr是一个拥有两个成员的元组
function add(x:number, y:number){
// ...
}
add(...arr) // 正确。TypeScript能够确定...arr可以匹配函数add()的参数数量,就不会报错了
2
3
4
5
另一种写法是使用as const断言。
const arr = [1, 2] as const; //这是一个只读的值类型,可以当作数组,也可以当作元组
有些函数可以接受任意数量的参数,这时使用扩展运算符就不会报错。
const arr = [1, 2, 3];
console.log(...arr) // 正确。console.log()可接受任意数量的参数,传入...arr不会报错
2