Array & Tuple
Array
- 几乎所有的编程语言中
[]都指代一个数组。 - 在TypeScript中,数组元素的类型在定义时必须确定。
- 在JavaScript中,数组以字典方式存储,且长度是动态的。
声明及初始化
可使用Array<T>或T[]来声明数组类型,该语法适用于任何类型。
const arr: string[] = ['a', 'b', 'c']
const brr: Array<number> = [1, 2, 3]
类型推断
数组也可使用类型推断来推断其类型。
// intArr: number[]
const intArr = [1, 2, 3]
// Type 'string' is not assignable to type 'number'.
intArr[1] = 'a'
只读数组
ReadonlyArray可用来声明不能改变的数组,如插入元素、删除元素等都不可操作,可简写为readonly Type[]:
const arr: ReadonlyArray<string> = ['a', 'b', 'c']
// Property 'push' does not exist on type 'readonly string[]'.
arr.push('d')
const brr: readonly string[] = ['a', 'b', 'c']
ReadonlyArray不像Array一样,它并不是一个构造函数:
new Array(1, 2, 3)
// 'ReadonlyArray' only refers to a type, but is being used as a value here.
new ReadonlyArray(1, 2, 3)
还有一点,在常规数组和只读数组之间,可赋值性并非双向的:
let x: readonly string[] = []
let y: string[] = []
x = y
// The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'
y = x
这是因为在TypeScript中,只读数组类型readonly string[]是一种特殊的数组类型,它只允许读取数组元素,而不允许修改数组元素。因此,只读数组类型是一种更加严格的类型,它不能被赋值给普通数组类型string[],因为这样会破坏只读数组的只读特性。相反,普通数组类型可以被赋值给只读数组类型,因为只读数组类型只是限制了对数组元素的修改,而不限制对数组的赋值操作。因此,将普通数组类型赋值给只读数组类型不会破坏只读数组的只读特性。
只读声明
可采用以下几种方式进行只读数组的类型声明。
type A = readonly string[];
type B = ReadonlyArray<string>;
type C = Readonly<string[]>;
Tuple
元组类型是一种特殊的数组类型,它确切地知道它包含多少个元素,以及在特定位置确切地包含哪些类型。因此,元组是一种不可变的数组,无法修改长度、增删元素,创建时其长度、元素类型已经确定,但可通过剩余元素实现可变长度的元组类型。
// 原生元组
const tuple: [string, number] = ['hello', 42];
type StringNumberPair = [string, number];
const pair: StringNumberPair = ['hello', 42];
console.log(pair[0]); // OK
console.log(pair[1]); // OK
// Tuple type 'StringNumberPair' of length '2' has no element at index '2'.
console.log(pair[2]); // Error
对于类型系统来说,StringNumberPair描述了一个数组:在索引为0的位置包含一个string,在索引为1的位置包含一个number。如果所以试图超出元素的数量,将产生一个错误。
由于元组本质上是一个数组,因此可使用解构:
function doSomething(pair: StringNumberPair) {
const [name, age] = pair;
console.log(name, age);
}
简单的元组类型可使用interface来模拟:
interface StringNumberPair {
length: 2;
0: string;
1: number;
push(value: string | number): void;
}
剩余元素
元组可以使用剩余元素,展开元素可以放到任何地方,但这些元素必须是数组或元组类型:
type StringNumberPair = [string, number, ...boolean[]];
const pair: StringNumberPair = ['hello', 42, true, false, true];
由于剩余元素个数不固定,因此带有剩余参数的元组长度不固定。
可选属性
元组也可以有可选属性,但必须出现在类型的末尾,它会影响类型的长度:
type Either2dOr3dCoord = [number, number, number?];
function setCoordinate(coord: Either2dOr3dCoord) {
console.log(coord.length); // 2 or 3
}
可选属性与剩余元素的存在,可以让TypeScript将元组与参数列表项对应:
function readButtonInput(...args: [string, number, ...boolean[]]) {
const [name, version, ...input] = args;
}
只读元组
元组类型也可以通过添加只读修饰符将其声明为只读的:
let as: readonly number[] = [1, 2, 3];
// Property 'push' does not exist on type 'readonly number[]'.
as.push(4);
function doSomething(pair: readonly [string, number]) {
// Cannot assign to '1' because it is a read-only property.
pair[1] = 100;
}
在大多数的代码中,元组往往被创建并且不被修改,因此将很多情况下将类型注解为只读元组是一个很好的默认设置。还有一点非常重要,使用const断言的字面量数组将被推断为只读的元组类型,使用时可能存在类型不兼容问题。
const point = [3, 4] as const;
// function doSomething([x, y]: [number, number]) {}
function doSomething([x, y]: readonly [number, number]) {}
/**
* 类型不兼容
* [3, 4] as const -> readonly [3, 4] -> 不可改
* [3, 4] -> [3, 4] -> 可改
*/
doSomething(point);
只读声明
type D = readonly [number, string];
type E = Readonly<[number, string]>;
使用场景
- 函数返回多个值
- 手动解析一定格式的数据
- 指定参数和返回值类型
区别
- 数组的类型定义限制数组内部的元素可用哪些类型。
- 元组则是将结构和数据类型一起进行约束,相比数组类型更加严格。