在 TypeScript 语言模型中,类型(视为对象)和类型之间的转换关系(视为态射)构成一个范畴,也就是说,你可以像在 JavaScript 中操作 object 那样在 TypeScript 中操作类型,正如同在 JavaScript 语言模型中,可将 object 视为对象,将 function 视为态射。
同样不严谨地说,类型在 TypeScript 语言中可以视作基本的、平凡的东西,TypeScript 语言本身提供了一些方式使得对这类「类型对象」的操作变得便捷,这些「类型对象」多少类似于 JavaScript 中的普通对象那样,可以被创建,可以取出部分,可以被当作参数传进函数中并且作为函数返回值返回给所谓的调用者,我们在这篇文章中主要就是举例这些方法的使用。
你可以在 TypeScript Playground 中尝试或者验证文中的代码,并且通过将鼠标 hover 到类型名称上查看该类型名称背后指代的具体类型。本文中的大部分代码都可以在这个GitHub 仓库中的 node_modules/type-functions 目录下找到定义,并且这个仓库也在不断更新中。
这篇文章尝试帮助读者增进对于 TypeScript 类型系统以及 TypeScript 类型编程方法的熟悉程度,并且尝试给元编程爱好者带来乐趣。
TypeScript 的类型系统之所以强大,其原因之一就是语言层面提供了针对类型对象的模式匹配功能,具体来说就是 infer
关键字,这个模式匹配功能可以实现很灵活、丰富的类型操纵,下面通过例子来看怎么用。
Tuple 型类型对象是 TypeScript 中看起来比较简单的,我们接下来通过它来演示 infer
关键字和 extends
关键字的使用。
Tuple 型类型对象描述的是一个 Tuple 对象有几个值,第几个值分别对应什么类型,刚才我们获取到的函数参数类型是以 Tuple 类型的形式存在的,那么我们现在想尝试实现对 Tuple 类型的操纵,就如同对具体 Tuple 对象的操纵一样:
下列是两个 Tuple 类型对象的连接操纵,它尝试 infer 出两个 Tuple 类型对象中的元素,然后拿 infer 到的结果组建新的 Tuple 类型对象:
// 连接两个 Tuple
type ConcatTuple<T, S> = T extends [...args: infer X] ? (S extends [...args: infer Y] ? [...X, ...Y] : never ) : never
通过下列方式来使用它:
type TupleA = [a: number, c: boolean]
type TupleB = [d: string[], e: string]
type TupleA_B = ConcatTuple<TupleA, TupleB> // [a: number, c: boolean, d: string[], e: string]
type TupleD = [string, string, string[]]
type TupleE = [boolean[], number, number[][]]
type TupleD_E = ConcatTuple<TupleD, TupleE> // [string, string, string[], boolean[], number, number[][]]
以下代码演示如何尝试获取一个给定 Tuple 的第一个元素,如果取不到就返回 never
:
// 取 Tuple 首元素
type GetFirstFromTuple<T> = T extends [infer HeadT, ...infer _] ? HeadT : never;
type G1 = GetFirstFromTuple<[number,string[], string[][], number]>;
type G2 = GetFirstFromTuple<[ string[][], number]>;