元组中数组_TypeScript 3.0 元组类型的用法和一些奇技淫巧

就在三天前,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 的定义中,类型推导分别为参数 TUV 推出了类型 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];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值