先记住这个答案
条件类型的被检查位置是裸类型参数时,将联合类型代入通常会分别对每个成员计算,再合并结果。例如 T extends unknown ? { value: T } : never 会把字符串或数字变成两种包装对象的联合。把检查双方包在元组中,如 [T] extends [unknown],可以避免这一层分发,得到一个 value 为联合类型的对象。直接写某个具体联合表达式不等于裸参数。never 作为空联合在分发中可能直接得到 never,而整体检查又可能选择真分支,这些差异会影响工具类型的过滤与判空行为。
- 裸类型参数的条件检查可以逐个处理联合成员
- 元组包裹让当前这一层按整体关系比较
- never 的空联合行为需要单独验证
包装联合为什么不只是写法差异
把每种候选分别包装,可以在更复杂结构中保留候选之间的关联;把联合放进一个字段,则描述同一结构中的不确定值。简单单字段场景可能看起来相近,但一旦添加标签、约束或多个关联字段,差异就会明显影响赋值与收窄。
示例同时展示包装、过滤和 never 检查。过滤类型把非字符串分支变成 never,再由联合合并规则移除。两个 never 判断写法则形成典型反例:分发写法面对空联合没有成员可计算,元组包裹才是在问整个类型是否为 never。
type Split<T> = T extends unknown ? { value: T } : never;
type Together<T> = [T] extends [unknown] ? { value: T } : never;
type KeepText<T> = T extends string ? T : never;
type Separate = Split<string | number>;
type Combined = Together<string | number>;
type Filtered = KeepText<'ready' | 7 | 'done'>;
type NaiveNever<T> = T extends never ? true : false;
type IsNever<T> = [T] extends [never] ? true : false;
type EmptyDistributed = NaiveNever<never>;
type EmptyWhole = IsNever<never>;Separate 是两个包装成员的联合,Combined 的 value 是 string | number。Filtered 保留两个文本字面量,EmptyDistributed 是 never,EmptyWhole 才是 true;这些结果需要按分发粒度理解。
什么时候应该主动禁用分发
如果问题是所有可能值是否都能满足某个目标,逐个判断后得到真假联合可能不是所需结果。例如要求整个输入联合都属于字符串集合,应考虑整体比较;若问题是从候选中挑出字符串成员,则分发正好表达筛选意图。
方括号在这里构造单元素元组,使检查位置不再是裸 T。它不会在运行时把数据装进数组,也不是任何出现方括号的类型都能统一禁止所有计算。嵌套工具里若再次把 T 放到裸参数条件中,那个内部条件仍可能独立分发。
特殊类型与规模是实际边界
never 的空联合行为容易让用于检测类型的工具得到意外结果,any 则可能使条件结果同时受到两个分支影响,不能照搬普通具体类型的判断。编写通用工具时应专门验证 never、unknown、any 和包含多个成员的联合。
多层分发与模板字符串组合还可能快速放大类型数量,增加编辑器和编译器负担。应优先表达真实需要的关系,必要时拆分工具或限制候选规模,而不是为了获得一个看似精确的大联合无限展开。类型工具也需要可读性和可维护边界。
容易答错的地方
- 所有条件类型遇到联合都会自动分发
- 分发有特定触发形式,直接检查具体联合或包裹后的参数不等同于裸类型参数;应看被检查位置的真实写法。
- T extends never 能直接检测所有 never 输入
- 当 T 是裸参数并代入 never 时,空联合可能让整个结果仍为 never;若要整体判断,需要使用能够避免该层分发的形式。
面试官还会怎么问?
分发里的 never 为什么会消失?
联合结果中的 never 没有可用候选,通常不会增加任何值,所以过滤工具可以用它删除不匹配成员;这与空联合本身不执行成员计算要分开看。
元组包裹会改变实际函数接收的参数吗?
不会,这里只是类型层的结构包装,不生成数组或参数转换;实际调用签名和函数体必须另外描述运行时行为。
怎样验证一个复杂过滤工具?
至少覆盖单成员、混合联合、全部不匹配、never、unknown 与 any,并检查结果是否保留需要的关联,而不只是看一个正常样例。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。