TypeScript 类型体操入门:从 typeof 到 infer
类型体操不是炫技,它解决的是“让代码更难写错”。本文用循序渐进的例子讲清 typeof、keyof、索引访问类型、条件类型、infer 的推断技巧,并给出一套做题思路与常见坑。
18 分钟阅读
小明
TypeScript 类型体操入门:从 typeof 到 infer
你会在 TS 学习路上遇到一个分水岭:
- 前半段:给变量/函数加类型
- 后半段:读懂一串像“加密文本”的类型表达式
比如:
type Flatten<T> = T extends (infer U)[] ? U : T
很多人看到 infer 就开始皱眉:
- 这是在运行时做推断吗?
- 为什么它能“从类型里拆出类型”?
- 面试为什么老考?
这篇文章的目标是:
- 让你理解“类型体操”到底在解决什么
- 掌握
typeof→keyof→ 条件类型 →infer的核心套路 - 能自己写出 5~8 个常用工具类型
1. 先定调:类型体操在干什么?
类型体操本质上是:
在 TypeScript 的类型系统 里写“函数”,把输入类型变换为输出类型。
它发生在编译期,不影响运行时性能。
典型诉求:
- 从 API 返回类型里提取 data
- 从函数类型里拿到参数/返回值
- 把联合类型变成映射对象
- 让某个字段必填/可选/只读
2. typeof:从值到类型
typeof 在 TS 里最常用于“从一个值拿到它的类型”。
const config = {
env: 'prod',
retry: 3,
}
type Config = typeof config
// { env: string; retry: number }
注意:
- 这是类型层面的
typeof - 不是运行时 JS 的
typeof运算(虽然语法一样)
配合 as const 会更强:
const roles = ['admin', 'user'] as const
type Role = typeof roles[number]
// 'admin' | 'user'
这里的 [number] 是索引访问类型:
T[number]表示数组元素类型
3. keyof:拿到对象的 key 联合
type User = { id: string; name: string }
type UserKeys = keyof User
// 'id' | 'name'
工程里常见用法:做“安全的 key 访问”。
4. 条件类型:类型层面的 if
条件类型长这样:
type IsString<T> = T extends string ? true : false
你可以把它当作:
- 如果
T可以赋值给string,则返回true,否则false
更实用的例子:提取 Promise 的 resolve 类型:
type AwaitedLike<T> = T extends Promise<infer U> ? U : T
这里就引入了核心主角:infer。
5. infer:在条件类型里“声明一个待推断变量”
infer 只能出现在 extends 的右侧,意义是:
让编译器在匹配结构时,把某一部分“抓出来”绑定给一个类型变量。
例子:从数组类型里拿元素类型:
type ElementOf<T> = T extends (infer U)[] ? U : never
type A = ElementOf<number[]> // number
type B = ElementOf<string[]> // string
推演:
- 如果
T能匹配成U[] - 那
U就是元素类型
5.1 从函数类型中提取参数与返回值
type Params<T> = T extends (...args: infer P) => any ? P : never
type Return<T> = T extends (...args: any[]) => infer R ? R : never
type F = (a: string, b: number) => boolean
type P = Params<F> // [string, number]
type R = Return<F> // boolean
你会发现:很多内置工具类型就是这么做的。
6. 分布式条件类型:联合类型会被“逐个处理”
这是类型体操的第二个分水岭。
当 T 是联合类型,并且 T 直接出现在 extends 左侧时:
type ToArray<T> = T extends any ? T[] : never
type X = ToArray<string | number>
// string[] | number[]
它会对联合里的每个成员分别计算,再把结果 union 回去。
这是“神奇”的来源,也是坑的来源。
如果你不想分布式,可以把 T 包一层:
type NoDistribute<T> = [T] extends [any] ? T : never
7. 做题思路:先画“输入结构”,再写匹配
类型体操不靠灵感,靠套路:
- 明确输入类型长什么样(数组?Promise?函数?对象?)
- 用
extends去匹配结构 - 用
infer把关键部分抓出来 - 组合成输出
练习题(建议你自己写一遍):
First<T>:取元组第一个元素Last<T>:取元组最后一个元素UnwrapPromise<T>:取 Promise resolve 类型DeepReadonly<T>:递归只读
8. 面试如何讲类型体操?
你可以说:
- TS 类型体操是编译期的类型变换。
- 主要靠条件类型 +
infer提取结构信息。 - 工程价值是提升类型表达力,减少运行时错误。
这比“我会写 infer”更像工程师。
总结
typeof:从值拿类型keyof:拿 key 联合- 条件类型:类型层面的 if
infer:从结构里抓出一部分类型- 分布式条件类型:联合会逐个处理
如果你愿意,我可以下一篇继续写:
- 10 个高频工具类型手写(Pick/Omit/ReturnType/Parameters/Exclude/Extract…)
- 并讲清每个在业务里的落地场景