再论 ts 体操
长期以来对于 ts 一直停留在「入门」阶段,实际并不理解,ts 为什么在社区中被称为「类型体操」。本文就 ts 类型体操进行梳理,以作回顾。
ts 为什么被称为「类型体操」?
这其实是因为 js 这门语言过于灵活导致的,ts 作为 js 的超集,旨在编译过程中做类型检查,并不会改变 js 的语法。我们可以先看一下其它静态语言的特点:
简单增加类型系统
仅仅对定义的变量、函数和类等进行类型声明。类型不匹配时会报错。(这也是笔者此前简单使用 ts 的途径)这种类型系统,过于死板了。比如同一个加法函数,对整数和浮点数需要分别声明,这里以 c++ 为例:
int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; }
支持泛型的类型系统
ts 中也有泛型,泛型旨在通过一种通用的类型,来减少重复的代码。这里不过多介绍。
T add<T>(T a, T b) { return a + b; } add(1,2); add(1.111, 2.2222);
java 就是这种类型系统,但是这对于 js 来说还是不够。因为 在 java 中对象都是通过类 new 出来的,但 js 可以凭空创建对象,并且在 ts 中,有时还需要对泛型 T 进行一些逻辑处理。由此诞生了 ts 这种「支持类型编程的类型系统」
支持类型编程的类型系统
在 ts 中,经常能看到对传入的类型参数(泛型)做各种逻辑运算,产生新的类型,这就是类型编程。
比如下面这个类型体操题目:实现一个类型
Flatten<T>
,把嵌套的数组类型展开成一个一维数组:type Flatten<T> = T extends [infer First, ...infer Rest] ? [...Flatten<First>, ...Flatten<Rest>] : [T] type Result = Flatten<[1, [2, [3, 4]], 5]> // 结果是 [1, 2, 3, 4, 5]
这就像在类型层面实现了一个“数组扁平化”,完全不依赖 JS 运行逻辑,全靠类型推导实现,非常绕脑,但也很优雅。
ts 体操套路
虽然 ts 体操虽然复杂,但是也是有套路的。
extends ? :
)
1. 条件类型(T extends U ? X : Y
- 核心是 判断类型。
- 用来做类型分支、类型过滤、模式匹配等。
应用:判断类型、过滤联合类型、递归控制流。
2. 类型推断 infer
T extends SomeType<infer U> ? U : never
- 用于从类型中“提取”子类型,类似于模式匹配。
- 常用于数组、函数、对象结构的拆解。
应用:提取参数、返回值、数组元素、对象属性等。
3. 递归类型
- 类型可以调用自己,做递归处理。
type Flatten<T> = T extends [infer First, ...infer Rest]
? [...Flatten<First>, ...Flatten<Rest>]
: [T]
应用:数组展开、深度映射、路径处理等。
4. 联合类型分发特性
条件类型在联合类型中会自动分发:
type ToArray<T> = T extends any ? T[] : never
type A = ToArray<1 | 2 | 3> // 结果是 1[] | 2[] | 3[]
应用:对联合类型分别处理,比如类型过滤、映射等。
5. 字符串模板类型
type Hello<T extends string> = `Hello ${T}`
应用:路径拼接、字符串变换(驼峰/下划线等)。
6. 映射类型
type Readonly<T> = {
readonly [K in keyof T]: T[K]
}
应用:属性变换、key 重命名、deep readonly、optional 处理等。
7. keyof、in、typeof 等关键字
type Keys = keyof { a: number; b: string } // 'a' | 'b'
应用:属性遍历、类型映射、对象处理等。
8. 分布式条件 + 递归的组合拳
很多高级类型体操题都依赖这两个一起使用,比如实现类型过滤器、转置器、路径分解器等。
总结
对于新手而言,还是推荐先看阮一峰老师的 ts 入门教程Typescript 教程。而后,在了解 ts 类型体操后,可以在 afu 老师的 type-challenges 仓库中进行练习~