源代码如下:
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 表示同时具有 A 和 B 的属性。
如何提取并组合交叉类型?
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 的实现机制是:
- 首先对每个
T的成员生成一个函数类型(x: T) => any。 - 然后通过条件类型的推断
infer,从这些函数中提取参数类型R。 - 最终,所有
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 属性
小结
- 提取
R:通过(x: T) => any提取参数类型R。 - 分布计算:对
T的每个成员单独提取参数类型。 - 组合为交叉类型:所有提取的
R通过交叉类型操作符&组合成最终结果。
这一过程确保了联合类型中的每个成员被处理,并最终返回一个严格的交叉类型。通过这种方式,UnionToIntersection 实现了从联合类型到交叉类型的高效转换。
















