用 TypeScript 写业务代码一两年了,interface、type、泛型这些基础用得还算熟。但每次看到项目里 infer、Exclude、ReturnType 这些,还是有点迷。最近花时间系统学了一遍条件类型,记录一下理解过程。
条件类型的基本形式
条件类型的语法看起来就像三元表达式:
1 | type IsString<T> = T extends string ? 'yes' : 'no' |
T extends U ? X : Y 的意思是:如果 T 能赋值给 U(T 是 U 的子类型),结果就是 X,否则是 Y。
联合类型会触发分发(distributive):
1 | type IsString<T> = T extends string ? 'yes' : 'no' |
infer:在条件类型里提取类型
infer 关键字只能在条件类型的 extends 子句里用,作用是声明一个”待推断的类型变量”,让 TypeScript 去推断并捕获它:
1 | // 内置的 ReturnType 就是用 infer 实现的 |
再看几个常用例子:
提取函数参数类型:
1 | type Parameters<T> = T extends (...args: infer P) => any ? P : never |
提取 Promise 包裹的类型:
1 | type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T |
(这个 Awaited 在 TypeScript 4.5 已经内置了)
提取数组元素类型:
1 | type ElementType<T> = T extends (infer Item)[] ? Item : never |
几个常用的内置工具类型实现
理解了条件类型,很多内置工具类型就可以自己实现了:
1 | // Exclude:从 T 中去掉能赋值给 U 的类型 |
实际业务里能用上的场景
提取接口所有 key 的值类型:
1 | interface Config { |
过滤出对象里值为函数的 key:
1 | type FunctionKeys<T> = { |
Deep Readonly:递归让所有嵌套属性只读:
1 | type DeepReadonly<T> = { |
类型编程和值编程的对应关系
很多人觉得类型体操太抽象,其实可以类比值层面的操作:
| 值层面 | 类型层面 |
|---|---|
if / else |
条件类型 T extends X ? A : B |
typeof |
keyof T, T[K] |
| 函数参数 | 泛型 <T> |
| 模式匹配 | infer |
| 递归函数 | 递归类型别名 |
| 数组遍历 | 映射类型 { [K in keyof T]: ... } |
把类型想成”在类型系统里跑的程序”,很多东西就看懂了。
条件类型和 infer 这套东西上手有点难,但掌握了之后能干的事多很多:写出更精确的类型约束,减少 as any 的使用,让 TypeScript 真正发挥出它的价值。
评论加载中…