联合类型与交叉类型
本教程共 80 篇 · 第 15 篇 · 更新于 2026-08-10 · 约 10 分钟阅读
本节目标:学会用
|组合多种可能性(联合类型),用&组合多个类型的特性(交叉类型),理解联合类型的”共有成员”限制和类型收窄,掌握交叉类型在对象组合中的正确用法。
联合类型:A 或 B
联合类型用 | 竖线连接多个类型,表示”这个值可以是 A,也可以是 B”。
// id 可以是 string,也可以是 number
let id: string | number;
id = "abc123"; // ✅
id = 456; // ✅
// id = true; // ❌ boolean 不在联合中
函数的参数经常用联合类型来增加灵活性:
function printId(id: string | number) {
console.log(`ID: ${id}`);
}
printId("user_001"); // ✅
printId(42); // ✅
// printId({ id: 1 }); // ❌ 对象不在联合中
联合类型的”陷阱”:只能访问共有成员
拿到一个联合类型的值后,你只能访问每个成员类型都有的属性和方法。这听起来反直觉——毕竟是”并集”,怎么变成”交集”了?
function getLength(value: string | number) {
// value.length; // ❌ number 没有 length 属性
// 如果 value 是 number,访问 length 会出问题
return String(value).length; // ✅ 确保是 string 再操作
}
这个行为其实很合理:TypeScript 不知道运行时这个值到底是 string 还是 number,所以只允许调用两方都有的操作(比如 toString()、valueOf())。这也叫”进门前先检查所有可能”。
换句话说,联合类型的值是并集(可以是任何一个),但可用的操作是交集(只有共有的才能用)。
类型收窄:让联合类型恢复”具体类型”
要想使用某个成员独有的方法,必须先收窄类型——用代码判断出它到底是哪一种:
function processId(id: string | number) {
if (typeof id === "string") {
// 这个分支里,id 被收窄为 string
console.log(id.toUpperCase());
} else {
// 这个分支里,id 被收窄为 number
console.log(id.toFixed(2));
}
}
typeof 是最常用的收窄方式。针对对象的联合类型,你可以用 in 操作符或字面量字段区分:
type Success = { status: "ok"; data: string };
type Error = { status: "error"; message: string };
type Response = Success | Error;
function handleResponse(res: Response) {
if (res.status === "ok") {
// 这里 res 被收窄为 Success
console.log("数据:", res.data);
} else {
// 这里 res 被收窄为 Error
console.log("错误:", res.message);
}
}
这种模式有个专门的名字——可辨识联合(discriminated union)。每个成员有一个共享的”辨识字段”(这里的 status),TypeScript 通过这个字段来判断具体类型。
联合类型作为”选项集”
联合类型最常见的用途之一是把变量限制在一组固定值中:
type Direction = "up" | "down" | "left" | "right";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Alignment = "left" | "center" | "right";
function align(text: string, direction: Alignment) {
console.log(`${text} 对齐到 ${direction}`);
}
align("标题", "center"); // ✅
// align("标题", "middle"); // ❌ "middle" 不在 Alignment 中
配合编辑器的自动补全,这种写法的开发体验非常好——输到一半就能看到所有可选值。
交叉类型:A 且 B
交叉类型用 & 连接多个类型,表示”这个值同时满足 A 和 B”。
type Person = {
name: string;
age: number;
};
type Worker = {
company: string;
role: string;
};
// Employee 同时有 Person 和 Worker 的所有属性
type Employee = Person & Worker;
let alice: Employee = {
name: "Alice",
age: 28,
company: "Google",
role: "工程师"
};
交叉类型在组合对象类型时最有用——你有一个”基础属性”类型和一个”扩展属性”类型,用 & 一交叉就得到了完整类型。
跟接口继承有什么不同
接口可以用 extends 继承,交叉类型用 & 组合,效果类似:
// 接口继承
interface Employee2 extends Person, Worker { }
// 交叉类型
type Employee3 = Person & Worker;
主要区别:接口支持声明合并(同样的接口名多次声明会自动合并),交叉类型不支持。对初学者来说,对象组合时用哪种差别不大,挑顺手的。
交叉类型的冲突处理
当两个类型的同名属性类型不兼容时,交叉会得到 never:
type A = { value: string };
type B = { value: number };
type C = A & B;
// C 的类型是 { value: never }
// 因为 value 不可能同时是 string 和 number,所以被推断为 never
let obj: C = {
// value: ??? ❌ 无法赋值——没有任何值同时是 string 和 number
};
如果同名属性的类型兼容(比如一个比另一个更具体),交叉会取交集:
type A = { value: string | number };
type B = { value: number };
type C = A & B;
// C 的类型是 { value: number }——取其交集
number 既是 string | number 的子集,也是 number 本身,所以交集是 number。
联合与交叉的混合使用
两种类型可以组合使用。先看一个实用例子——函数重载的替代方案:
// 联合作参数,交叉作返回值
type Config = { host: string; port: number };
type Overrides = { host?: string; port?: number; debug?: boolean };
function mergeConfig(base: Config, overrides: Overrides): Config & Partial<Overrides> {
return { ...base, ...overrides };
}
let result = mergeConfig(
{ host: "localhost", port: 3000 },
{ port: 8080, debug: true }
);
// result 的类型包含 host, port, debug
再来看一个分配律的规则——交叉对联合有分配律:
type T = (A | B) & C;
// 等价于 (A & C) | (B & C)
知道这个有助于理解复杂类型的推导结果。
可运行示例
// 联合类型与交叉类型综合示例
// === 联合类型 ===
// 1. 基本联合
function formatInput(input: string | number): string {
if (typeof input === "string") {
return `"${input}"`; // 收窄为 string
}
return input.toFixed(2); // 收窄为 number
}
console.log(formatInput("hello")); // "hello"
console.log(formatInput(3.14159)); // "3.14"
// 2. 可辨识联合
type ApiResult =
| { status: "success"; data: string[] }
| { status: "error"; code: number; message: string };
function handleApiResult(result: ApiResult) {
switch (result.status) {
case "success":
console.log(`成功!数据: ${result.data.join(", ")}`);
break;
case "error":
console.log(`失败 [${result.code}]: ${result.message}`);
break;
}
}
handleApiResult({ status: "success", data: ["A", "B", "C"] });
handleApiResult({ status: "error", code: 404, message: "未找到" });
// === 交叉类型 ===
// 3. 对象组合
type Timestamp = { createdAt: Date; updatedAt: Date };
type AuthorInfo = { author: string; version: number };
type Document = Timestamp & AuthorInfo & { title: string };
let doc: Document = {
title: "TypeScript 入门",
author: "码上学",
version: 1,
createdAt: new Date("2026-08-10"),
updatedAt: new Date("2026-08-10")
};
console.log(`文档: ${doc.title} by ${doc.author} v${doc.version}`);
// 4. 联合字面量作选项(最常见的"联合用法")
type Theme = "light" | "dark";
type Size = "sm" | "md" | "lg";
function renderButton(text: string, theme: Theme, size: Size) {
console.log(`[${theme}/${size}] ${text}`);
}
renderButton("确定", "dark", "lg");
// renderButton("确定", "blue", "lg"); // ❌ 编译错误
输出:
"hello"
3.14
成功!数据: A, B, C
失败 [404]: 未找到
文档: TypeScript 入门 by 码上学 v1
[dark/lg] 确定
小结
联合类型 A | B 扩大取值范围,交叉类型 A & B 合并类型特性。联合类型的关键是”先收窄再使用”,交叉类型的关键是”所有属性都得有”。联合字面量是最简单也最常用的类型约束工具之一。下一章是基础类型的最后一站——字面量类型,你会看到 as const 怎么把普通值变成精确的类型约束。