联合类型和交叉类型有什么区别?
简化版
联合类型 A | B 表示值可以是 A 或 B,使用时只能访问共同安全属性;交叉类型 A & B 表示同时具备 A 和 B 的能力,常用于组合对象类型。
详细版
联合类型:
type ID = string | number;
交叉类型:
type User = { id: number };
type Named = { name: string };
type NamedUser = User & Named;
联合类型需要收窄后才能访问特有属性:
function f(x: string | number) {
if (typeof x === 'string') {
x.toUpperCase();
}
}
交叉类型通常用于 mixin、扩展配置、组合 props。要注意冲突属性可能变成 never。
完整版教学
一、联合类型表达“多选一”
一个接口状态可能是加载中、成功、失败:
type State =
| { status: 'loading' }
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
当前值只会是其中一种,所以访问 data 前必须确认 status === 'success'。
二、交叉类型表达“都要有”
type WithTime = { createdAt: string };
type Article = { title: string } & WithTime;
Article 必须同时有 title 和 createdAt。它适合把多个对象能力拼在一起。
三、为什么联合类型不能随便访问属性
对于 A | B,TS 只允许访问 A 和 B 都有的成员。因为运行时如果值是 B,而你访问 A 特有成员,就会出错。
所以联合类型通常要配合类型收窄。
四、交叉类型冲突问题
如果两个类型同名属性不兼容:
type X = { id: string } & { id: number };
id 可能变成 never,因为它不可能同时是 string 和 number。这类类型通常说明设计有问题。
五、面试追问与工程落地
联合类型常见追问是“为什么联合类型只能访问公共属性”。因为运行时值可能是联合中的任意一个成员,只有所有成员都拥有的属性才是安全的。想访问特有属性,必须先通过判别字段或类型守卫收窄。
交叉类型常被误解成“对象合并”。类型层面的 A & B 表示同时满足 A 和 B,但运行时不会帮你合并对象。真正合并值仍然需要 { ...a, ...b } 或其他逻辑。
工程里判别联合非常适合接口状态建模。比起多个可选字段散落在一个对象里,{ status: 'success'; data } | { status: 'error'; error } 更能让编译器帮你防止遗漏分支。
六、用值集合与可访问成员解释反直觉结果
从可能值看,A | B 扩大了可接受值集合,A & B 要求值同时满足两个约束,因此缩小了集合。但从“无需收窄即可访问的属性”看,联合只能保证各成员共有的能力,交叉则汇集双方能力;这正是很多人把两者记反的原因。
type A = { common: string; a: number }
type B = { common: string; b: boolean }
declare const union: A | B
union.common
// union.a // 报错:运行时可能是 B
declare const both: A & B
both.a
both.b
| 观察角度 | A | B | A & B |
| --- | --- | --- |
| 可接受的值 | 满足 A 或 B | 同时满足 A 和 B |
| 直接保证的成员 | 共有安全成员 | 双方全部成员 |
| 常用建模 | 多状态、多个输入形态 | 能力组合、增强对象 |
| 主要风险 | 未收窄访问特有成员 | 冲突属性变为 never |
若用 loading、data?、error? 三个独立字段建模,布尔和可选组合会产生多种无效状态;改成 3 个判别联合成员,实际只允许加载、成功、失败这 3 种合法状态。联合类型的价值不只是少写字段,而是让不可能状态无法构造。
记忆钩子:联合扩大“值的入口”却缩小“直接可用能力”,交叉缩小“值的入口”却汇集“可用能力”。
七、常见误区与追问
- 误区:A | B 类型的值同时拥有 A 和 B 的所有属性。 运行时只保证它属于至少一个成员,未收窄前只能使用所有成员都安全的能力。
- 误区:A & B 会在运行时自动合并两个对象。 它只描述静态约束,真正创建合并值仍需执行对象组合逻辑。
- 误区:交叉类型出现 never 一定是编译器故障。 不兼容同名属性要求一个值同时满足互斥类型,自然会得到不可能的 never。
- 追问:为什么判别联合优于多个可选字段? 每个成员把字段依赖绑定在同一状态中,减少无效组合并支持准确收窄。
- 追问:联合类型如何做穷尽检查? 在 switch 的 default 分支把剩余值赋给 never,新增成员未处理时编译器会报错。
- 追问:交叉对象与对象展开的冲突规则相同吗? 不相同;交叉要求类型同时成立,对象展开在运行时通常由后写属性覆盖前写属性。
- 追问:string 与 number 的交叉是什么? 两者没有共同的实际值,结果是 never。
八、加强记忆
联合类型是“可能是这个或那个”,交叉类型是“必须既有这个又有那个”。联合要收窄,交叉要注意属性冲突。
从可能值集合判断入口大小,再从安全成员判断可用能力,就不会被“或/且”的表面文字绕晕。