什么是Tuple元组
Tuple元组对应到JavaScript中就是一个受到限制的数组类型。所谓受到限制就是指Tuple元组类型的数组中每个元素的类型和数组的长度在编译期就是确定的。
举例如下:
// 元组类型
type MyArgs = [string, number, string, boolean];
// 正确
const args: MyArgs = ['jason', 5, 'male', true];
// TypeScript报错。类型错误
const wrongTypeArgs: MyArgs = [{}, 'jason', 5, true];
// TypeScript报错。数组长度应该是4,而这里是5
const toLongArgs: MyArgs = ['jason', 5, 'male', true, 'ff'];
上面的MyArgs
就是元组类型,一个有明确长度和元素类型的数组。
一眼看去会觉得Tuple元组没有使用场景。 那就请看下面的例子:
function foo(...args: [string, number]): void {
// ...
}
// 正确调用
foo('a', 9);
// TypeScript报错
foo('a', 'b', 33);
例子中的...args
是rest
语法,要给...args
进行类型声明,元组是最合适的方式,[string, number]
就是一个元组类型。它声明foo
的参数必须第一个是string
,第二个是number
,没有第三个参数。
Tuple元组用在上面的例子中特别合适,但是还是有些欠缺,就是仅仅看参数声明不能看出每个参数的意义,因此TypeScript 4.0 版本加了一个新功能,每个Tuple元组中的类型都可以声明一个label标识:
// name和score是label标识
function foo(...args: [name: string, score: number]): void {
// ...
}
要注意的是,name
和score
并不是函数的参数名,事实上它们对类型检查也没有任何影响,它们只是一个标识,只是为了增加代码的可读性,就跟注释一样,或许可以用于生成文档。通过这些标识,我们就能大概知道,foo
的第一个参数是一个名字,第二个参数是分数。
老版本的问题
标识还是挺有用的,但是TypeScript 4.0有一个奇怪的规定,就是一个Tuple元组中,要么每个类型都没有标识,要么每个类型都有标识。然而这会导致一个问题,就是当我们的元组类型中有spread
时:
type OtherInfo = [hobby: string, age: number];
// 这里会报错:元组成员必须全部具有或全部不具有名称。ts(5084)
function foo(...args: [name: string, score: number, ...OtherInfo]): void {
// ...
}
这里的...OtherInfo
部分在之前版本的TypeScript会报错,提示你:元组成员必须全部具有或全部不具有名称
。
所以只能是像下面这样来绕过这个问题:
type OtherInfo = [hobby: string, age: number];
// 给OtherInfo加一个标签unusedLabel
function foo(...args: [name: string, score: number, ...unusedLabel: OtherInfo]): void {
// ...
}
这个新加的unusedLabel
毫无意义,是一段很不好的代码。
新版的改进
这次的TypeScript 5.2 新版本解决了这个问题,Tuple元组的各个类型可以有label标签,也可以没有labe标签,老版本中要么有要么没有的限制被去除了,而且spread
也不会导致label标签消失。(spread
就是指...OtherInfo
)。
举例如下:
type OtherInfo = [hobby: string, age: number];
// foo参数的实际类型:[name: string, number, hobby: string, age: number]
function foo(...args: [name: string, number, ...OtherInfo]): void {
// ...
}
上面例子中foo
的参数实际类型是:[name: string, number, hobby: string, age: number]
,OtherInfo
中的label标签也会被保留,最终的Tuple元组中,有的有label,有的没有label,也不会有问题。
这就是这些了。