就在三天前,Anders合并了一个PR到Typescript主分支,也就是3.0版本。从3.0版本开始,Typescript 支持函数 Rest 参数和 Spread 表达式使用元组类型。
注:以下内容主要是翻译自 Anders 的PR。在本文的最后分享一些关于这个特性的奇技淫巧。
这个PR包括以下改进:
元组类型的 Rest 形参到独立的形参的展开:
当函数的 Rest 形参具有元组类型时,元组类型被展开为一系列独立的形参。例如,以下两个定义是等价的:
declare function foo(...args: [number, string, boolean]): void;
declare function foo(args_0: number, args_1: string, args_2: boolean): void;
元素类型的 Spread 表达式到独立的参数的展开:
当函数调用的最后一个参数是对元组类型的一个 Spread 表达式时,Spread 表达式的结果对应一系列具有元组的元素类型的独立参数。也就是说,以下两种调用是等价的:
const args: [number, string, boolean] = [42, "hello", true];
foo(42, "hello", true);
foo(args[0], args[1], args[2]);
foo(...args);
泛型支持 Rest 形参,相应的支持元组类型的推导:
Rest 形参允许具有一个约束为数组类型的泛型类型,并且类型推导可以给这样的泛型 Rest 形参推导元组类型。这开启了高阶的部分形参列表的捕获和散列的特性:
declare function bind<T, U extends any[], V>(f: (x: T, ...args: U) => V, x: T): (...args: U) => V;
declare function f3(x: number, y: string, z: boolean): void;
const f2 = bind(f3, 42); // (y: string, z: boolean) => void
const f1 = bind(f2, "hello"); // (z: boolean) => void
const f0 = bind(f1, true); // () => void
f3(42, "hello", true);
f2("hello", true);
f1(true);
f0();
在以上的 f2
的定义中,类型推导分别为参数 T
,U
,V
推出了类型 number
,[string, boolean]
,void
。
注意当一个元组类型是从一系列形参推导出,然后展开为一个参数列表时,正如例子中的 U
,原本的形参名称会被用于展开的(然而,命名并没有语义上的意义,也无法以其他方式观察到)。
元组类型支持可选元素类型:
元组类型如今允许在元素类型上后缀一个 ? 来指定元素是可选的:
let t: [number, string?, boolean?];
t = [42, "hello", true];
t = [42, "hello"];
t = [42];
在 --strictNullChecks
模式下,?
会自动在元素类型中包含 undefined
,类似于(函数的)可选参数。
元组类型允许忽略一个后缀了 ?
修饰符的元素,同时,在其右边的所有元素也应该具有 ? 修饰符。
当尝试把可选形参推导为元组类型时,可选形参在推出的类型中成为可选元组元素。
具有可选元素的元组类型的 length
属性(的类型)是一个表示可能的长度的数字字面量类型的 union 类型。例如,在元组类型 [number, string?, boolean?]
中,length
属性的类型是 1 | 2 | 3
。
元组类型支持 Rest 元素:
元组类型的最后一个元素可以是形式为 ...X
的 Rest 元素,X
是一个数组类型。Rest 元素指定了元组类型是无限扩展的,可能有零个或更多个具有数组元素类型的额外元素。例如,[number, ...string[]]
表示元组具有一个 number
元素和随后任意多个 string
元素。
function tuple<T extends any[]>(...args: T): T {
return args;
}
const numbers: number[] = getArrayOfNumbers();
const t1 = tuple("foo", 1, true); // [string, number, boolean]
const t2 = tuple("bar", ...numbers); // [string, ...number[]]
具有 Rest 元素的元组类型的 length
属性的类型是 number
。
一些奇技淫巧:
之前旧的代码用了递归定义效率比较低,而且因为TS泛型类型实例化深度有限制,不能支持较长的元组。这里放上重写过的版本,有时间的话我可能会解释一下原理……
// 取得第一个元素
type TupleHead<T extends any[]> = T[0];
// 去除第一个元素
type TupleTail<T extends any[]> = ((...t: T) => void) extends (x: any, ...t: infer R) => void ? R : never;
// 取得最后一个元素
type TupleLast<T extends any[]> = T[TupleTail<T>['length']];
// 去除最后一个元素
type TupleInit<T extends any[]> = TypeAssert<Overwrite<TupleTail<T>, T>, any[]>;
// 从头部加入一个元素
type TupleUnshift<T extends any[], X> = ((x: X, ...t: T) => void) extends (...t: infer R) => void ? R : never;
// 从末尾加入一个元素
type TuplePush<T extends any[], X> = TypeAssert<Overwrite<TupleUnshift<T, any>, T & { [x: string]: X }>, any[]>;
// 连接两个元组
type TupleConcat<A extends any[], B extends any[]> = {
1: A,
0: TupleConcat<TuplePush<A, B[0]>, TupleTail<B>>
}[B extends [] ? 1 : 0];
// 用到的 helper 类型,简化代码和解决某些情况下的类型错误
type TypeAssert<T, A> = T extends A ? T : never;
type Overwrite<T, S extends any> = { [P in keyof T]: S[P] };
以及一个 union 转全排列的 tuple,会生成 n! 个元素的union,仅供娱乐……
type Overwrite<T, S extends any> = { [P in keyof T]: S[P] };
type TupleUnshift<T extends any[], X> = T extends any ? ((x: X, ...t: T) => void) extends (...t: infer R) => void ? R : never : never;
type TuplePush<T extends any[], X> = T extends any ? Overwrite<TupleUnshift<T, any>, T & { [x: string]: X }> : never;
type UnionToTuple<U> = UnionToTupleRecursively<[], U>;
type UnionToTupleRecursively<T extends any[], U> = {
1: T;
0: UnionToTupleRecursively_<T, U, U>;
}[[U] extends [never] ? 1 : 0]
type UnionToTupleRecursively_<T extends any[], U, S> =
S extends any ? UnionToTupleRecursively<TupleUnshift<T, S> | TuplePush<T, S>, Exclude<U, S>> : never;
let x: UnionToTuple<1 | 2 | 3 | 4> = [1, 2, 3, 4];
let y: UnionToTuple<1 | 2 | 3 | 4> = [4, 3, 2, 1];