条件类型是什么?infer 有什么作用?
简化版
条件类型形如 T extends U ? X : Y,表示类型层面的条件判断。infer 用于在条件类型中推断某一部分类型,常用于提取函数返回值、Promise 内部值、数组元素类型。
详细版
条件类型:
type IsString<T> = T extends string ? true : false;
infer 示例:
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
如果 T 是函数类型,infer R 会推断返回值类型。
提取 Promise 值:
type AwaitedValue<T> = T extends Promise<infer R> ? R : T;
条件类型遇到联合类型会分发,这是很多工具类型的基础。
完整版教学
一、条件类型是类型系统里的 if
普通 JS 可以写:
condition ? a : b
TS 类型层面也可以根据类型关系选择不同结果:
type ToArray<T> = T extends any ? T[] : never;
二、extends 在条件类型里表示可赋值判断
T extends U 不是类继承,而是判断 T 是否可以赋值给 U。
type A = 1 extends number ? true : false; // true
三、infer 是局部类型变量
infer 只能在条件类型的 extends 子句中使用。它像是告诉 TS:这里有一块类型,请帮我推出来。
type ElementType<T> = T extends (infer U)[] ? U : T;
ElementType<string[]> 得到 string。
四、分发条件类型
当 T 是裸类型参数并传入联合类型时,条件类型会对联合成员逐个计算:
type ToArray<T> = T extends any ? T[] : never;
type R = ToArray<string | number>; // string[] | number[]
这就是 Exclude、Extract 等工具类型能工作的原因。
五、面试追问与工程落地
条件类型常见追问是“如何关闭分发”。把类型参数包在元组里即可:
type NoDistribute<T> = [T] extends [string] ? true : false;
这样传入联合类型时不会逐个成员分发,而是把整个联合一起判断。
还会问 infer 能不能推多个位置。可以,甚至可以通过多个 infer 提取参数和返回值。但如果推断位置冲突,结果可能变成联合或更宽的类型。写复杂类型时要用小例子验证,不要凭感觉。
工程里条件类型适合封装库类型、请求返回解包、组件泛型推断。业务代码不要过度堆叠条件类型,否则报错信息会变得难以理解。类型体操的终点应该是提升调用体验,而不是炫复杂。
六、按代入、分发、匹配三步手算结果
手算条件类型先看被检查的一侧是不是“裸类型参数”。如果是,并且实际参数是联合类型,就先按成员分发;每个成员再做 extends 可赋值判断,最后把分支结果重新联合。把 T 包进单元素元组后,检查对象变成整个联合,分发随之关闭。
type Mark<T> = T extends string ? `S:${T}` : 'N'
type A = Mark<'a' | 'b' | 1>
// Mark<'a'> | Mark<'b'> | Mark<1>
// 'S:a' | 'S:b' | 'N'
type Whole<T> = [T] extends [string] ? 'all string' : 'not all string'
type B = Whole<'a' | 1> // 'not all string'
| 步骤 | Mark<'a' | 'b' | 1> 的结果 |
| --- | --- |
| 分发前 | 1 个含 3 个成员的联合 |
| 逐项判断 | 'a'、'b' 命中 true,1 命中 false |
| 重新合并 | 'S:a' | 'S:b' | 'N' |
infer 是匹配成功时声明的局部类型变量,可以在一次模式中推断多个位置。对重载函数做返回值推断时,TypeScript 通常从最后一个、最宽泛的调用签名推断,不会逐个重载做调用解析,因此结果可能比某个具体调用更宽。
心法:看到条件类型先圈出
extends左侧;裸T决定是否分发,infer只负责在匹配成功的结构里取出部件。
七、常见误区与追问
- 误区:条件类型中的 extends 一定表示继承。 这里判断的是类型可赋值关系,不是运行时类继承。
- 误区:任何联合类型都会触发分发。 只有被检查位置是裸类型参数且代入联合时才分发,
[T]、Promise<T>等包装会改变行为。 - 误区:infer 可以在任意类型别名中声明。 它只能出现在条件类型
extends子句的匹配位置。 - 追问:如何关闭分发条件类型? 把判断两侧包成元组,如
[T] extends [U] ? X : Y,让整个联合参与一次判断。 - 追问:Exclude 为什么能过滤联合成员? 它对联合逐项分发,把可赋给排除集合的成员映射成 never,联合中 never 随后消失。
- 追问:infer 能同时提取参数和返回值吗? 可以,在函数模式中分别写
infer P与infer R,但必须明确约束和失败分支。 - 追问:复杂条件类型为什么会拖累维护? 多层分发和递归会放大实例化数量、拉长报错信息,也可能触发类型实例化过深限制。
八、加强记忆
条件类型是类型里的三元表达式,infer 是在匹配过程中“顺手取出一块类型”。复杂工具类型很多都建立在条件类型、分发和 infer 上。
手算时坚持“识别裸参数、展开联合、逐项匹配、合并结果”四步,就能解释多数 infer 与工具类型题。