第67课:联合类型与交叉类型——| 与 &、类型收窄、字面量类型
在 TypeScript 中,类型并非总是单一的。现实世界的值往往具有多种可能的形式:一个变量可能是字符串或数字;一个函数的参数可能接受多个不同的接口;一个组合配置需要同时满足多个约束。联合类型(|)和交叉类型(&)是表达这种灵活性的核心工具。本节课将深入两者的语法与语义,重点讲解如何通过类型守卫(Type Guards)将联合类型安全地“缩窄”为具体类型,以及交叉类型在组合对象形状和混入模式中的实战应用。
1. 联合类型(Union Types):A | B
联合类型表示一个值可以是多种类型中的一种。语法为 typeA | typeB,读作“typeA 或 typeB”。
1 | function formatId(id: string | number): string { |
1.1 联合类型的属性访问限制
在没有缩窄类型之前,只能访问联合类型中所有成员共有的属性。如果访问某个成员独有的属性,TypeScript 会报错。
1 | interface Bird { |
1.2 联合类型与字面量类型
字面量类型(Literal Types)将一个值约束为特定的字符串、数字或布尔值。与联合类型结合,可以创建类似“枚举”的效果。
1 | type Direction = 'north' | 'south' | 'east' | 'west'; |
字面量联合类型远比传统的数字枚举更灵活——它不需要导入任何东西,在运行时零开销,并且在条件判断中享有完整的类型缩窄支持。
2. 类型收窄(Type Narrowing)
类型收窄是将联合类型的变量从“可能是多种类型”逐步缩小到“确定是某一种类型”的过程。TypeScript 通过控制流分析自动识别多种收窄模式。
2.1 typeof 类型守卫
适用于 JavaScript 原始类型(string、number、boolean、bigint、symbol、undefined、object、function)。
1 | function process(value: string | number | boolean) { |
2.2 instanceof 类型守卫
用于检查对象是否为某个类的实例。
1 | class Dog { |
2.3 in 操作符
检查对象是否包含某个属性,适用于区分不同接口的联合类型。
1 | interface Car { |
2.4 自定义类型谓词(Type Predicates)
当内置的 typeof/instanceof/in 无法表达复杂逻辑时,可以编写返回值为 arg is Type 的自定义函数。
1 | interface User { |
类型谓词 obj is User 告诉 TypeScript:“如果该函数返回 true,则参数 obj 一定是 User 类型”。这是最强大的类型收窄工具。
2.5 switch 与字面量联合类型的穷尽性检查
当联合类型是字面量联合时,switch 的 default 分支可以利用 never 类型进行穷尽性检查——确保所有可能的情况都被处理。
1 | type Shape = 'circle' | 'square' | 'triangle'; |
3. 交叉类型(Intersection Types):A & B
交叉类型将多个类型合并为一个类型,新的类型必须同时满足所有被合并类型的约束。语法为 typeA & typeB,读作“typeA 且 typeB”。
1 | interface Nameable { |
3.1 交叉类型的冲突处理
当合并的类型中同名属性的类型不兼容时,交叉结果可能是 never——表示无法构造满足所有约束的值。
1 | type A = { value: string }; |
原则:基本类型的交叉取其交集。string & number = never;string & string = string。对象的交叉取其属性合并,同名属性的类型递归交叉。
3.2 交叉类型用于组合多个约束
交叉类型非常适合描述“某对象必须同时满足多个接口”的场景,例如混入模式(Mixin)。
1 | interface Logger { |
3.3 交叉类型用于扩展类型别名
类型别名不能使用 extends,但可以使用交叉类型组合多个类型:
1 | type BaseConfig = { url: string }; |
4. 联合类型与交叉类型的结合
联合和交叉可以组合使用,但需要注意分配律:A & (B | C) 等价于 (A & B) | (A & C)。
1 | type Admin = { role: 'admin'; permissions: string[] }; |
5. 联合类型 vs 交叉类型 对比总结
| 特性 | 联合类型 A | B |
交叉类型 A & B |
|---|---|---|
| 语义 | “要么是 A,要么是 B” | “既是 A,又是 B” |
| 属性访问 | 只能访问共有的属性 | 可以访问所有属性 |
| 适用场景 | 函数参数可接受多种类型、状态机、变体数据 | 组合多个接口、混入模式、扩展类型别名 |
| 冲突处理 | 无冲突概念(成员不会同时存在) | 同名属性类型不兼容 → never |
| 常见配合 | 类型守卫(typeof、instanceof、类型谓词) | 泛型约束中的多重要求 |
课后练习
一、概念自测(选择题 / 填空题)
(单选) 以下哪个 TypeScript 类型守卫用于检查对象的类实例?
A.typeof
B.instanceof
C.in
D.is(单选)
{ name: string } & { age: number }的结果类型是什么?
A.{ name: string } | { age: number }
B.{ name: string, age: number }
C.never
D.{ name: string, age: number } | never(填空) 要定义一个变量只可能是字符串
'left'或'right',其类型应写作______。(多选) 以下哪些方法可以用于缩窄联合类型?
A.typeof val === 'string'
B.val instanceof Array
C.'property' in val
D. 类型谓词函数val is Type
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要编写一个 TypeScript 函数 describeShape(shape),接收一个联合类型的图形对象,返回其描述字符串。要求如下:
- 定义
Circle、Rectangle、Triangle三个接口,各有不同的属性(如radius、width/height、base/height)。 - 定义类型
Shape = Circle | Rectangle | Triangle。 describeShape接收Shape类型,使用类型守卫(in或自定义类型谓词)缩窄每种类型,返回描述字符串(包含面积计算)。- 使用
default+never实现穷尽性检查。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 TypeScript 代码。提示词中需明确指定三个接口的结构、类型守卫的方式和穷尽性检查。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请创建 TypeScript 文件
src/describeShape.ts,实现一个完整的类型收窄示例。要求:
- 定义
interface Circle { kind: 'circle'; radius: number; }、interface Rectangle { kind: 'rectangle'; width: number; height: number; }、interface Triangle { kind: 'triangle'; base: number; height: number; }。- 定义联合类型
type Shape = Circle | Rectangle | Triangle;。- 实现函数
describeShape(shape: Shape): string,使用switch (shape.kind)对每种类型进行收窄,返回包含面积计算的描述字符串(如'Circle with radius 5 has area 78.54')。default分支使用const exhaustive: never = shape;实现穷尽性检查。- 添加 JSDoc 注释。在末尾添加三个调用示例并打印结果。
- 代码使用严格 TypeScript,完成后输出完整文件内容。
四、面试真题与参考答案
题目(字节跳动前端面试题):
请解释 TypeScript 中联合类型和交叉类型的区别。什么是类型收窄?请列举至少三种类型收窄的方式并给出示例。
参考答案:
- **联合类型
A | B**:表示一个值可以是多种类型中的一种。只能安全访问所有成员共有的属性。适用于函数参数可接受多种类型、状态机、字面量枚举等场景。 - **交叉类型
A & B**:表示一个值必须同时满足所有合并类型的约束。可以访问所有类型的全部属性。适用于组合多个接口、混入模式、扩展类型别名。 - 类型收窄:将联合类型从“可能多种”缩小到“确定一种”的过程。TypeScript 通过控制流分析自动识别收窄模式。
三种收窄方式:
typeof守卫:if (typeof val === 'string') { /* val 被收窄为 string */ }instanceof守卫:if (val instanceof Date) { /* val 被收窄为 Date */ }- 自定义类型谓词:
function isUser(obj: unknown): obj is User { return ...; }—— 返回true后 obj 被收窄为User。 in操作符:if ('radius' in shape) { /* shape 被收窄为 Circle */ }switch+ 字面量判别:通过shape.kind在switch各分支自动收窄。
课后练习答案
一、概念自测答案
B
- 解析:
instanceof检查对象是否为某类的实例,用于类实例的类型缩窄。
- 解析:
B
- 解析:两个对象类型的交叉类型合并所有属性,结果为
{ name: string, age: number }。
- 解析:两个对象类型的交叉类型合并所有属性,结果为
'left' | 'right'- 解析:字面量联合类型,变量只能取这两个字符串之一。
A、B、C、D
- 解析:四种方式均为 TypeScript 支持的类型缩窄手段。
typeof用于原始类型,instanceof用于类实例,in用于属性存在性检查,类型谓词用于自定义逻辑。
- 解析:四种方式均为 TypeScript 支持的类型缩窄手段。
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请编写 TypeScript 函数describeShape(shape: Shape): string,实现联合类型的收窄。要求:
- 定义
Circle({ kind: 'circle', radius: number })、Rectangle({ kind: 'rectangle', width: number, height: number })、Triangle({ kind: 'triangle', base: number, height: number })。type Shape = Circle | Rectangle | Triangle。- 使用
switch (shape.kind)分支收窄,计算面积并返回描述字符串。default分支使用const _: never = shape做穷尽性检查。- 添加 JSDoc 注释,提供三个调用示例注释。输出完整代码。”