联合类型转交叉类型 Union to Intersection
大约 3 分钟TC-Hard
联合类型转交叉类型 Union to Intersection
题目
实现一个高级工具类型 UnionToIntersection<U>,将联合类型转换为交叉类型。
type I = UnionToIntersection<'foo' | 42 | true>
// 期望结果:'foo' & 42 & true解答
type UnionToIntersection<U> =
(U extends any ? (arg: U) => void : never) extends (arg: infer I) => void
? I
: never这个方案利用了 TypeScript 的两个关键特性:分布式条件类型和函数参数位置的逆变推断。
逐步拆解
第一步 — 将联合类型分布到函数类型中
U extends any ? (arg: U) => void : never当 U 是联合类型 A | B | C 时,分布式条件类型会对每个成员独立映射:
((arg: A) => void) | ((arg: B) => void) | ((arg: C) => void)U extends any 永远为真——它的作用纯粹是为了触发分布。
第二步 — 从逆变位置推断
... extends (arg: infer I) => void ? I : never现在我们有一个函数的联合类型,需要从中推断出参数类型 I。TypeScript 必须找到一个类型 I,使得 (arg: I) => void 能被联合中的每个成员赋值。
函数参数是逆变的:如果 (arg: A) => void 是 (arg: I) => void 的子类型,那么 I 必须是 A 的子类型(方向反转)。要同时满足所有三个函数,I 必须同时是 A、B 和 C 的子类型——这正好就是 A & B & C。
推导过程:
U = 'foo' | 42 | true
第一步:((arg: 'foo') => void) | ((arg: 42) => void) | ((arg: true) => void)
第二步:推断 I 使其满足所有三个函数
→ I = 'foo' & 42 & true ✓深入分析
逆变是关键
TypeScript 的类型推断行为取决于 infer 所在位置的型变方向:
| 位置 | 型变方向 | 多个候选合并方式 |
|---|---|---|
| 返回类型 | 协变 | 联合(|) |
| 参数 | 逆变 | 交叉(&) |
如果把 infer 放在返回位置:
type Wrong<U> =
(U extends any ? () => U : never) extends () => infer I ? I : never
type Test = Wrong<'foo' | 42> // 'foo' | 42 — 还是联合类型!返回位置是协变的,多个候选会合并为联合——跟原来一样。我们需要逆变的参数位置来将其翻转为交叉。
为什么需要 U extends any?
没有分布式条件类型,U 会被当作一个整体处理:
type Broken<U> = ((arg: U) => void) extends (arg: infer I) => void ? I : never
type Test = Broken<'foo' | 42> // 'foo' | 42 — 没有发生分布extends any 包装是必须的,它让联合类型先分布成独立的函数类型。
实际应用场景
联合转交叉在合并配置对象时非常有用:
type Config = { host: string } | { port: number } | { debug: boolean }
type Merged = UnionToIntersection<Config>
// { host: string } & { port: number } & { debug: boolean }它也是其他高级类型(如 UnionToTuple)的基础构建块。
不可能的交叉
当联合成员是不兼容的原始类型时,交叉会坍缩为 never:
type I = UnionToIntersection<string | number> // string & number → never这是预期行为——没有值能同时是 string 和 number。
核心要点
- 分布式条件类型(
T extends any ? ... : never)对联合类型的每个成员独立处理 - 逆变推断在函数参数位置将多个
infer候选通过交叉(&)合并 - 协变推断在返回类型位置通过联合(
|)合并——效果相反 - 这个模式是许多高级 TypeScript 工具类型的基础构建块
- 理解型变(协变 vs 逆变位置)是解决困难级类型挑战的关键
