高级类型编程
类型体操训练场
为什么需要「类型体操」?
让编译器成为你最严格的代码审查员
零运行时开销
所有类型检查在编译时完成,打包产物中不包含任何类型信息,对性能零影响。
API 契约
精确的类型定义就是最好的文档,调用者无需翻阅源码就知道如何正确使用。
重构安全网
修改接口或函数签名后,编译器会标出所有受影响的调用点,避免遗漏。
Part 01
泛型 — 类型系统的「函数」
Part 02
条件类型 — 类型的 if-else
语法结构
条件类型的语法形如T extends U ? X : Y,它让类型系统拥有了分支判断的能力。 当 T 可赋值给 U 时结果为 X,否则为 Y。
T extends U ?
✅ true
→ X
|❌ false
→ Y
conditional-basic.ts
// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
// 分发条件类型(Distributive)
// 当 T 是联合类型时,条件类型会逐一应用
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>;
// = string[] | number[] ← 分发结果
// 而非 (string | number)[]
// 用 never 过滤类型
type NonNullable2<T> = T extends null | undefined ? never : T;
type D = NonNullable2<string | null | undefined>;
// = stringconditional-advanced.ts
// infer 关键字:在条件类型中「捕获」子类型
// 提取函数返回值类型
type ReturnType2<T> = T extends (...args: any[]) => infer R
? R
: never;
type R1 = ReturnType2<() => string>; // string
type R2 = ReturnType2<(x: number) => void>; // void
// 提取 Promise 内部类型
type UnwrapPromise<T> = T extends Promise<infer U>
? UnwrapPromise<U> // 递归解包
: T;
type U1 = UnwrapPromise<Promise<Promise<number>>>;
// = number
// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;
type E1 = ElementType<string[]>; // string
type E2 = ElementType<[1, "a"]>; // 1 | "a"Part 03
映射类型 — 批量变换类型属性
Part 04
模板字面量类型 — 字符串级的类型运算
类型也能拼字符串?
TypeScript 4.1 引入模板字面量类型,允许你像写template literal一样在类型层面拼接字符串。结合映射类型和条件类型,威力巨大。
template-literal.ts
// 基础拼接
type Greeting = `Hello, ${string}!`;
const g1: Greeting = "Hello, World!"; // ✅
// const g2: Greeting = "Hi, World!"; // ❌
// 结合联合类型 → 笛卡尔积
type Color = "red" | "green" | "blue";
type Size = "sm" | "md" | "lg";
type ColorSize = `${Color}-${Size}`;
// "red-sm" | "red-md" | "red-lg" |
// "green-sm" | ... | "blue-lg"
// 共 3×3 = 9 种组合
// 内置字符串工具类型
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
// 实战:自动推导事件处理器名称
type PropEventSource<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]: (
value: T[K]
) => void;
};
interface FormData {
username: string;
age: number;
}
type FormEvents = PropEventSource<FormData>;
// {
// onUsernameChange: (value: string) => void;
// onAgeChange: (value: number) => void;
// }Part 05
infer 关键字 — 类型的模式匹配
Part 06
递归类型 — 无限嵌套的类型力量
用递归解决复杂类型变换
当你需要对深层嵌套的结构(如 JSON、嵌套数组、路径字符串)做类型变换时, 递归类型是唯一的解法。核心思路:在条件类型中引用自身,设置好终止条件即可。
deep-readonly.ts
// 深度 Readonly
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface Config {
db: {
host: string;
port: number;
options: {
ssl: boolean;
pool: number;
};
};
}
type ReadonlyConfig = DeepReadonly<Config>;
// 所有层级的属性都变为 readonly
// 深度 Partial
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;path-type.ts
// 用递归生成对象路径联合类型
type Paths<T, Prefix extends string = ""> = T extends object
? {
[K in keyof T & string]:
| `${Prefix}${K}`
| Paths<T[K], `${Prefix}${K}.`>;
}[keyof T & string]
: never;
interface State {
user: {
name: string;
address: {
city: string;
zip: string;
};
};
theme: "light" | "dark";
}
type StatePaths = Paths<State>;
// "user" | "user.name" | "user.address"
// | "user.address.city" | "user.address.zip"
// | "theme"
// 类型安全的 deep get
function deepGet<T, P extends Paths<T>>(
obj: T,
path: P
): /* ... 返回值类型可进一步推导 */ any {
return path.split(".").reduce(
(o, k) => (o as any)?.[k], obj
);
}Playground
类型体操挑战
Roadmap
学习路径建议
推荐学习资源
官方
TypeScript 官方手册
Advanced Types 章节是第一手权威资料
练习
type-challenges
GitHub 上最流行的类型体操题库,由易到难
课程
Total TypeScript
Matt Pocock 的付费课程,实战导向的进阶教程
书籍
深入理解 TypeScript
国内社区整理的免费电子书,覆盖全面