先记住这个答案
条件类型通常写成 T extends U ? X : Y,含义是根据 T 对 U 的可赋值性关系选择类型结果。它不是执行某个 JavaScript 布尔表达式,也不会在运行时创建分支或改变数据。把结构判断放在条件内部,可以让工具接受更广输入,在满足条件时提取字段,不满足时返回明确的失败类型;这与直接要求泛型参数必须满足约束不同。泛型尚未确定时,结果可能暂时无法归约,函数体里的普通判断也不保证自动证明所有复杂条件返回关系。
- extends 在此判断类型关系而非运行时继承
- 条件分支的结果是类型,不生成数据变换
- 内部条件允许处理不满足结构的输入类型
为什么条件内部可以读取专有字段
工具想获取对象的 message 字段类型,但输入也可能没有该字段。先在条件中检查结构,成功分支就能在这份证据下进行索引访问;失败分支返回 never,表示当前输入没有对应结果。这样不必把所有调用者都限制为带 message 的对象。
示例对不同结构计算不同结果,字符串消息得到字符串类型,数字消息得到数字类型,没有字段则得到 never。接收字符串消息类型的变量不能赋数字,说明条件选择仍然产生正常静态约束,并没有让类型系统变成任意通过的描述。
type MessageOf<T> = T extends { message: unknown } ? T['message'] : never;
type TextMessage = MessageOf<{ message: string; code: number }>;
type NumericMessage = MessageOf<{ message: number }>;
type MissingMessage = MessageOf<{ code: number }>;
const text: TextMessage = 'ready';
// @ts-expect-error TextMessage 是 string
const invalid: TextMessage = 7;三个别名分别归约为 string、number 和 never。这里没有任何运行时对象解析过程,数字赋给文本类型的预期错误只验证静态结果,不能证明外部响应已经带有正确字段。
与外层泛型约束有什么不同
如果直接写 T extends { message: unknown } 作为类型参数约束,不含字段的输入在使用工具时就会被拒绝。把判断放进条件类型,则可以接受这个输入,并把失败解释为 never 或其他约定结果。两者对应不同接口设计:禁止输入与计算失败结果。
失败类型也应有明确含义。随意使用 any 会让后续失去检查,使用 never 则可能在更大联合运算中被消去。需要保留失败原因时,可以返回带标签的类型结构。应根据工具如何被组合选择结果,不能只以哪种写法最短为准。
类型分支不能替代程序分支
JavaScript 三元表达式根据实际值选择并计算某一边,条件类型在编译后消失。把接口响应套进条件类型,不会自动检查字段、转换字符串或选择运行处理器。程序仍需要真实的条件、解析和错误路径,类型工具只是帮助描述这些行为。
当返回类型依赖尚未确定的泛型参数时,函数实现可能无法用简单 if 自动证明条件返回正确。遇到这种情况,应检查是否更适合重载、明确的结果联合或更简单签名,不要直接对每个返回值断言来遮住无法证明的关系。
容易答错的地方
- 条件类型会在浏览器中执行三元判断
- 它只参与类型计算,生成的 JavaScript 不包含对应验证代码;需要根据实际输入选择行为时,仍要写真实运行时判断。
- extends 总是在要求类继承关系
- 条件类型中的 extends 主要表达可赋值性判断,普通结构类型也可以参与;应根据语法位置区分泛型约束、条件判断和类继承。
面试官还会怎么问?
失败分支为什么经常返回 never?
它可以表达没有匹配类型,并在后续联合运算中自然被消去;如果调用者需要区分失败原因,应设计更显式的结果类型。
把输入设成联合时会逐个判断吗?
某些写法会触发分发,尤其是检查位置为裸类型参数时;这是一项独立规则,应确认是逐成员处理还是希望把整个联合一起比较。
条件类型能直接判断某个变量当前值吗?
它处理静态类型信息,无法读取任意运行时值;保留字面量的变量可以提供更具体类型,但动态输入仍需要实际检查和解析。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。