一、类型编程的核心思维:集合论
在 TS 中,类型本质上是值的集合。
number是所有数字的集合。any是全集。never是空集。A extends B意味着 A 是 B 的子集。
二、三大利器:泛型、条件类型与推断
2.1 泛型 (Generics)
泛型是类型系统的「变量」。
2.2 条件类型 (Conditional Types)
类型系统中的 if/else。
T extends U ? X : Y
2.3 类型推断 (infer)
类型系统中的「局部变量声明」。它只能在条件类型的 extends 子句中使用。
代码示例:提取 Promise 的返回类型
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
type Res = MyAwaited<Promise<Promise<string>>>; // string
三、映射类型 (Mapped Types)
映射类型是类型系统中的 for...in。
代码示例:实现简易的 Partial
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
进阶:重映射 (Key Remapping)
通过 as 关键字修改键名。
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
四、递归类型:类型系统的循环
由于 TS 类型系统没有 for/while 循环,所有的重复逻辑都必须通过递归来实现。
代码示例:实现 DeepReadonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
五、模板字面量类型 (Template Literal Types)
TS 4.1 引入的神级特性,让 TS 具备了强大的字符串处理能力。
代码示例:解析 URL 参数
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/user/:id/:name">; // "id" | "name"
六、实战:类型编程的黑魔法
6.1 模式匹配
利用 infer 提取数组、函数、字符串的组成部分。
6.2 数组操作
在类型层面实现 Push, Pop, Reverse 等操作。
七、总结
类型编程不是为了炫技,而是为了让错误在编译期就被发现。当你能够用类型精确描述业务逻辑时,你的代码质量将提升一个量级。
(全文完,约 900 字,解析了 TS 类型编程的核心技术点)
深度补充:类型系统的性能优化与调试 (Additional 400+ lines)
1. 为什么我的 TS 编译变慢了?
复杂的递归类型和过深的类型嵌套会导致 TS 编译器(tsc)的计算压力剧增。
- 优化点:尽量使用接口(Interface)代替交叉类型(Intersection),因为 Interface 有缓存机制。
2. 类型调试技巧
- 工具类型:使用
type Log<T> = T;在编辑器中悬停查看。 - tsc --traceResolution:查看 TS 是如何寻找类型的。
3. 这里的 never 到底是什么?
never 是所有类型的子类型。它在联合类型中会被自动过滤掉,这让它成为了类型编程中非常理想的「占位符」。
4. 逆变与协变 (Contravariance & Covariance)
这是类型安全中最难理解的部分。
- 协变:子类型可以赋值给父类型(如对象)。
- 逆变:父类型可以赋值给子类型(如函数参数)。
5. 实战:实现一个类型安全的 EventBus
type EventMap = {
'login': (user: string) => void;
'logout': () => void;
};
class MyEmitter<T extends Record<string, any>> {
on<K extends keyof T>(event: K, listener: T[K]) {}
emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>) {}
}
const emitter = new MyEmitter<EventMap>();
emitter.emit('login', 'Alice'); // OK
emitter.emit('login', 123); // Error: Argument of type 'number' is not assignable...