TypeScript 类型体操入门:从泛型约束到 infer 模式匹配
2026 年 07 月 22 日 1 分钟阅读 391 次浏览
为什么要学类型体操
"类型体操"听起来像炫技,但它的本质是用类型描述业务约束。当你的类型能精确表达"这个函数只接受对象里存在的 key"时,一整类运行时错误在编译期就被消灭了。
基石一:泛型约束
extends 在泛型里是"约束"而非"继承":
// K 必须是 T 的键名之一
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Ada" };
getProp(user, "name"); // ✅ string
getProp(user, "age"); // ❌ 编译报错:'age' 不存在
基石二:条件类型
条件类型是类型层面的三元表达式:
type IsString<T> = T extends string ? true : false;
type A = IsString<"hi">; // true
type B = IsString<42>; // false
结合分布式特性,条件类型会对联合类型逐个分发:
type ToArray<T> = T extends any ? T[] : never;
type R = ToArray<string | number>; // string[] | number[]
核心:infer 模式匹配
infer 让你在条件类型里"捕获"一个类型变量,像正则的捕获组:
// 提取函数返回值类型(TS 内置 ReturnType 的原理)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = () => { ok: boolean };
type Res = MyReturnType<Fn>; // { ok: boolean }
// 提取 Promise 内部类型
type Awaited2<T> = T extends Promise<infer U> ? U : T;
type X = Awaited2<Promise<string>>; // string
实战:把蛇形对象 key 转成驼峰
组合模板字面量类型 + 递归 + infer:
type SnakeToCamel<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
: S;
type CamelKeys<T> = {
[K in keyof T as SnakeToCamel<K & string>]: T[K];
};
type Raw = { user_name: string; created_at: number };
type Nice = CamelKeys<Raw>;
// { userName: string; createdAt: number }
心法
- 能推断就别手写:
ReturnType、Parameters、Awaited优先用内置。 - 递归要有出口:模板字面量递归务必写好终止分支。
- 别过度:类型是给人读的。当类型比实现还难懂时,退一步用更朴素的写法。
类型体操的终点不是"写得多花哨",而是"让编译器替你守住业务规则"。
评论区未配置
在 .env 中设置
PUBLIC_WALINE_SERVER_URL
即可启用 Waline 评论。