源代码如下:

type UnionToIntersection<T> = (
    T extends any ? (x: T) => any : never
  ) extends (x: infer R) => any
    ? R
    : never;

UnionToIntersection<T> 的实现中,T 是一个联合类型,函数 (x: T) => any 被逐个处理每个 T 的成员,并从中提取参数类型 R。所有提取出来的 R 会被组合成一个 交叉类型

以下是详细的解释和逐步推导:


什么是交叉类型?

交叉类型 (Intersection Type) 是 TypeScript 中的一种类型操作符,用于将多个类型组合在一起,表示一个对象需要同时满足多个类型的约束。例如:

type A = { a: string };
type B = { b: number };

type C = A & B;
// C 的类型是 { a: string; b: number }

交叉类型 C 表示同时具有 AB 的属性。


如何提取并组合交叉类型?

1. 条件类型提取 R

UnionToIntersection 的关键代码是:

(x: T) => any extends (x: infer R) => any ? R : never

这里通过条件类型和 infer,我们从函数 (x: T) => any 中提取参数的类型 R

例如:

  • T{ a: string } 时,R 就是 { a: string }
  • T{ b: number } 时,R 就是 { b: number }

2. 联合类型成员逐个提取

由于 T 是联合类型,条件类型的分布特性会自动将 T 的每个成员单独处理(见上一部分解释的分布式条件类型)。假设 T = { a: string } | { b: number },那么:

  • 第一次处理 T 的成员 { a: string },提取 R{ a: string }
  • 第二次处理 T 的成员 { b: number },提取 R{ b: number }

3. 使用交叉类型组合结果

TypeScript 的条件类型最终会将每次提取的 R 组合为一个交叉类型。也就是说:

UnionToIntersection<{ a: string } | { b: number }>

最终会返回:

{ a: string } & { b: number }

代码示例

为了更清晰地理解,可以直接用代码观察结果:

type UnionToIntersection<T> = (
  T extends any ? (x: T) => any : never
) extends (x: infer R) => any
  ? R
  : never;

// 定义一个联合类型
type Union = { a: string } | { b: number };

// 使用 UnionToIntersection 转换
type Intersection = UnionToIntersection<Union>;
// Intersection 的结果是:{ a: string } & { b: number }

这里 Intersection 表示一个对象类型,要求同时具有 { a: string }{ b: number } 的属性。


如何组合类型?

TypeScript 的实现机制是:

  1. 首先对每个 T 的成员生成一个函数类型 (x: T) => any
  2. 然后通过条件类型的推断 infer,从这些函数中提取参数类型 R
  3. 最终,所有 R 会自动组合成一个交叉类型。

推导的核心逻辑:

  • { a: string } 对应的函数 (x: { a: string }) => any 提取 R{ a: string }
  • { b: number } 对应的函数 (x: { b: number }) => any 提取 R{ b: number }
  • TypeScript 会将这些 R 合并为 { a: string } & { b: number }

运行时效果

尽管这些类型操作仅发生在编译阶段,运行时无法直接观察类型推导的过程,但其结果能够增强类型安全性。例如:

type UnionToIntersection<T> = (
  T extends any ? (x: T) => any : never
) extends (x: infer R) => any
  ? R
  : never;

type Union = { a: string } | { b: number };
type Intersection = UnionToIntersection<Union>;

function test(value: Intersection) {
  console.log(value.a); // 必须存在 a 属性
  console.log(value.b); // 必须存在 b 属性
}

test({ a: "hello", b: 42 }); // ✅ 正确
test({ a: "hello" });        // ❌ 错误:缺少 b 属性

小结

  1. 提取 R:通过 (x: T) => any 提取参数类型 R
  2. 分布计算:对 T 的每个成员单独提取参数类型。
  3. 组合为交叉类型:所有提取的 R 通过交叉类型操作符 & 组合成最终结果。

这一过程确保了联合类型中的每个成员被处理,并最终返回一个严格的交叉类型。通过这种方式,UnionToIntersection 实现了从联合类型到交叉类型的高效转换。