联合类型 (Union Types) 和交叉类型 (Intersection Types)?
在 TypeScript 中,联合类型 (Union Types) 和 交叉类型 (Intersection Types) 是两种非常重要的组合类型的方式。它们允许你通过组合现有的类型来创建新的类型。
简单来说:
- 联合类型 (
|):表示“或”的关系(是 A 或者 是 B)。 - 交叉类型 (
&):表示“和”的关系(既是 A 又是 B,通常用于合并)。
1. 联合类型 (Union Types)
符号: |
联合类型表示一个值可以是几种类型之一。
基本用法
最常见的场景是允许一个变量接受不同类型的值(例如 string 或 number)。
typescript
// id 可以是数字,也可以是字符串
let id: number | string;
id = 101; // OK
id = "202-A"; // OK
// id = true; // Error: 不能将 boolean 赋值给 number | string
访问成员的限制
当你使用联合类型时,TypeScript 只能保证该变量拥有这些类型共有的成员。
typescript
function printId(id: number | string) {
// console.log(id.toUpperCase()); // Error: number 类型没有 toUpperCase 方法
console.log(id.toString()); // OK: toString 是 string 和 number 共有的方法
}
类型守卫 (Type Guards)
为了安全地使用特定类型的方法,你需要使用“类型守卫”来缩小类型范围(Narrowing)。
typescript
function printId(id: number | string) {
if (typeof id === "string") {
// 在这个块里,TS 知道 id 是 string
console.log("ID is string: " + id.toUpperCase());
} else {
// 在这个块里,TS 知道 id 是 number
console.log("ID is number: " + id.toFixed(2));
}
}
字面量联合类型
常用于限制变量只能是某几个特定的值(类似枚举)。
typescript
type Direction = 'up' | 'down' | 'left' | 'right';
type WindowStates = "open" | "closed" | "minimized";
2. 交叉类型 (Intersection Types)
符号: &
交叉类型将多个类型合并为一个类型。这就意味着这个类型的对象同时拥有了这几种类型的所有成员。
基本用法
常用于对象类型的合并(Mixins)或扩展接口。
typescript
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: number;
department: string;
}
// Staff 类型同时拥有 Person 和 Employee 的所有属性
type Staff = Person & Employee;
const newStaff: Staff = {
name: "Alice", // 来自 Person
age: 30, // 来自 Person
employeeId: 12345, // 来自 Employee
department: "IT" // 来自 Employee
};
同名属性冲突
如果两个类型有相同的属性名,但类型不同,交叉后的结果通常是 never(因为一个值不可能同时是两种冲突的类型)。
typescript
interface A {
id: string;
}
interface B {
id: number;
}
type C = A & B;
// const c: C = { id: ... }
// Error: id 的类型变成了 string & number,即 never。
// 你无法给 id 赋值,因为没有值既是 string 又是 number。
3. 总结与对比
| 特性 | 联合类型 (A | B) |
交叉类型 (A & B) |
|---|---|---|
| 逻辑关系 | OR (或) | AND (和) |
| 含义 | 值是 A 类型 或者 B 类型 | 值 同时包含了 A 和 B 的所有特性 |
| 属性访问 | 只能直接访问 A 和 B 的公共属性 | 可以访问 A 和 B 的所有属性 |
| 主要用途 | 处理多种可能的输入类型 (如 API 响应) | 组合对象、扩展功能、混入 (Mixins) |
| 类比 | 像“多选一”开关 | 像“拼图”,把两块拼在一起 |
4. 实际应用场景示例
场景 A:API 响应 (联合类型)
API 可能返回成功的数据,也可能返回错误信息。
typescript
interface SuccessResponse {
status: 'success';
data: { id: number; name: string };
}
interface ErrorResponse {
status: 'error';
message: string;
}
// 响应可能是成功,也可能是失败
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(res: ApiResponse) {
// 利用 status 字段作为可辨识联合 (Discriminated Union)
if (res.status === 'success') {
console.log(res.data.name); // TS 知道这里是 SuccessResponse
} else {
console.error(res.message); // TS 知道这里是 ErrorResponse
}
}
场景 B:React 组件 Props (交叉类型)
当你编写一个 UI 组件库时,想让你的组件支持原生 HTML 属性。
typescript
// 自定义属性
interface ButtonProps {
variant: 'primary' | 'secondary';
isLoading?: boolean;
}
// 将自定义属性与原生 button 标签的属性合并
// React.ComponentProps<'button'> 包含了 onClick, className, id 等所有原生属性
type Props = ButtonProps & React.ComponentProps<'button'>;
// 现在这个 props 既有 variant,又有 onClick
const MyButton = (props: Props) => { ... }