联合类型
概述
联合类型(Union Types)是 TypeScript 中一个强大的类型特性,它允许一个值可以是多种类型中的一种。使用 | 操作符将多个类型组合在一起,表示"或"的关系。联合类型让类型系统更加灵活,能够精确描述可能具有多种类型的值,同时保持类型安全。联合类型在处理不确定类型的数据、API 响应、状态管理等场景中非常有用。
基本语法
联合类型使用 | 操作符将多个类型组合:
typescript
// 基本语法:类型1 | 类型2 | 类型3
type StringOrNumber = string | number;
type Status = "pending" | "completed" | "cancelled";
type ID = string | number;基本用法
简单联合类型
最简单的联合类型是将两个或多个基础类型组合:
typescript
// 字符串或数字
let value: string | number;
value = "hello"; // ✅ 正确
value = 42; // ✅ 正确
value = true; // ❌ 错误:Type 'boolean' is not assignable to type 'string | number'
// 函数参数使用联合类型
function printId(id: string | number): void {
console.log(`ID: ${id}`);
}
printId(101); // ✅ 正确
printId("202"); // ✅ 正确
printId(true); // ❌ 错误字面量联合类型
联合类型经常与字面量类型结合使用,创建精确的值集合:
typescript
// 字符串字面量联合类型
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
console.log(`Moving ${direction}`);
}
move("up"); // ✅ 正确
move("north"); // ❌ 错误:Type '"north"' is not assignable to type 'Direction'
// 数字字面量联合类型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceRoll {
return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}
// 混合字面量类型
type Status = "success" | "error" | 404 | 500;对象联合类型
联合类型也可以用于对象类型,创建更复杂的类型定义:
typescript
// 对象联合类型
type Circle = {
kind: "circle";
radius: number;
};
type Rectangle = {
kind: "rectangle";
width: number;
height: number;
};
type Shape = Circle | Rectangle;
function calculateArea(shape: Shape): number {
if (shape.kind === "circle") {
// TypeScript 知道这里 shape 是 Circle 类型
return Math.PI * shape.radius ** 2;
} else {
// TypeScript 知道这里 shape 是 Rectangle 类型
return shape.width * shape.height;
}
}提示
使用"可辨识联合"(Discriminated Union)模式,通过共同的属性(如 kind)来区分不同的类型,可以让 TypeScript 更准确地推断类型。
类型守卫
在使用联合类型时,通常需要使用类型守卫(Type Guards)来缩小类型范围:
typescript
// 使用 typeof 进行类型守卫
function processValue(value: string | number): void {
if (typeof value === "string") {
// 在这个分支中,TypeScript 知道 value 是 string 类型
console.log(value.toUpperCase());
} else {
// 在这个分支中,TypeScript 知道 value 是 number 类型
console.log(value.toFixed(2));
}
}
// 使用 instanceof 进行类型守卫
class Dog {
bark(): void {
console.log("Woof!");
}
}
class Cat {
meow(): void {
console.log("Meow!");
}
}
type Pet = Dog | Cat;
function makeSound(pet: Pet): void {
if (pet instanceof Dog) {
pet.bark(); // TypeScript 知道 pet 是 Dog 类型
} else {
pet.meow(); // TypeScript 知道 pet 是 Cat 类型
}
}自定义类型守卫
可以创建自定义类型守卫函数来更精确地判断类型:
typescript
// 自定义类型守卫
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function processValue(value: string | number | boolean): void {
if (isString(value)) {
// TypeScript 知道 value 是 string
console.log(value.length);
} else if (isNumber(value)) {
// TypeScript 知道 value 是 number
console.log(value * 2);
} else {
// TypeScript 知道 value 是 boolean
console.log(value ? "true" : "false");
}
}使用示例
示例 1:API 响应处理
联合类型非常适合处理可能返回不同结构的 API 响应:
typescript
// 定义 API 响应类型
type SuccessResponse = {
status: "success";
data: {
id: number;
name: string;
email: string;
};
};
type ErrorResponse = {
status: "error";
message: string;
code: number;
};
type ApiResponse = SuccessResponse | ErrorResponse;
// 处理 API 响应
function handleResponse(response: ApiResponse): void {
if (response.status === "success") {
// TypeScript 知道这里是 SuccessResponse
console.log("User:", response.data.name);
console.log("Email:", response.data.email);
} else {
// TypeScript 知道这里是 ErrorResponse
console.error(`Error ${response.code}: ${response.message}`);
}
}
// 使用示例
const successResponse: ApiResponse = {
status: "success",
data: {
id: 1,
name: "John Doe",
email: "john@example.com"
}
};
const errorResponse: ApiResponse = {
status: "error",
message: "User not found",
code: 404
};
handleResponse(successResponse); // ✅ 正确
handleResponse(errorResponse); // ✅ 正确示例 2:表单输入处理
联合类型可以处理不同类型的表单输入:
typescript
// 定义输入类型
type TextInput = {
type: "text";
value: string;
placeholder?: string;
};
type NumberInput = {
type: "number";
value: number;
min?: number;
max?: number;
};
type CheckboxInput = {
type: "checkbox";
checked: boolean;
label: string;
};
type FormInput = TextInput | NumberInput | CheckboxInput;
// 处理表单输入
function processInput(input: FormInput): string {
switch (input.type) {
case "text":
// TypeScript 知道这里是 TextInput
return `Text: ${input.value}`;
case "number":
// TypeScript 知道这里是 NumberInput
const min = input.min ?? 0;
const max = input.max ?? 100;
return `Number: ${input.value} (range: ${min}-${max})`;
case "checkbox":
// TypeScript 知道这里是 CheckboxInput
return `Checkbox: ${input.label} = ${input.checked}`;
default:
// 使用 never 类型确保所有情况都被处理
const _exhaustive: never = input;
return "";
}
}
// 使用示例
const inputs: FormInput[] = [
{ type: "text", value: "Hello", placeholder: "Enter text" },
{ type: "number", value: 42, min: 0, max: 100 },
{ type: "checkbox", checked: true, label: "Accept terms" }
];
inputs.forEach(input => {
console.log(processInput(input));
});示例 3:事件处理系统
联合类型可以用于定义不同类型的事件:
typescript
// 定义事件类型
type ClickEvent = {
type: "click";
x: number;
y: number;
button: "left" | "right" | "middle";
};
type KeyEvent = {
type: "keydown" | "keyup";
key: string;
ctrlKey: boolean;
shiftKey: boolean;
};
type ScrollEvent = {
type: "scroll";
deltaX: number;
deltaY: number;
};
type UIEvent = ClickEvent | KeyEvent | ScrollEvent;
// 事件处理函数
function handleEvent(event: UIEvent): void {
switch (event.type) {
case "click":
// TypeScript 知道这里是 ClickEvent
console.log(`Clicked at (${event.x}, ${event.y}) with ${event.button} button`);
break;
case "keydown":
case "keyup":
// TypeScript 知道这里是 KeyEvent
const modifiers = [
event.ctrlKey && "Ctrl",
event.shiftKey && "Shift"
].filter(Boolean).join("+");
console.log(`${event.type}: ${modifiers ? modifiers + "+" : ""}${event.key}`);
break;
case "scroll":
// TypeScript 知道这里是 ScrollEvent
console.log(`Scrolled: deltaX=${event.deltaX}, deltaY=${event.deltaY}`);
break;
}
}
// 使用示例
const events: UIEvent[] = [
{ type: "click", x: 100, y: 200, button: "left" },
{ type: "keydown", key: "Enter", ctrlKey: true, shiftKey: false },
{ type: "scroll", deltaX: 0, deltaY: 50 }
];
events.forEach(handleEvent);示例 4:配置选项
联合类型可以用于定义灵活的配置选项:
typescript
// 定义配置类型
type StringConfig = {
type: "string";
value: string;
maxLength?: number;
};
type NumberConfig = {
type: "number";
value: number;
min?: number;
max?: number;
};
type BooleanConfig = {
type: "boolean";
value: boolean;
};
type Config = StringConfig | NumberConfig | BooleanConfig;
// 配置验证函数
function validateConfig(config: Config): boolean {
switch (config.type) {
case "string":
if (config.maxLength && config.value.length > config.maxLength) {
return false;
}
return true;
case "number":
if (config.min !== undefined && config.value < config.min) {
return false;
}
if (config.max !== undefined && config.value > config.max) {
return false;
}
return true;
case "boolean":
return true; // 布尔值总是有效的
default:
const _exhaustive: never = config;
return false;
}
}
// 使用示例
const configs: Config[] = [
{ type: "string", value: "Hello", maxLength: 10 },
{ type: "number", value: 42, min: 0, max: 100 },
{ type: "boolean", value: true }
];
configs.forEach(config => {
console.log(`${config.type}: ${validateConfig(config) ? "valid" : "invalid"}`);
});联合类型的限制
在使用联合类型时,只能访问所有类型共有的成员:
typescript
type Bird = {
fly(): void;
layEggs(): void;
};
type Fish = {
swim(): void;
layEggs(): void;
};
type Pet = Bird | Fish;
function move(pet: Pet): void {
// ❌ 错误:Property 'fly' does not exist on type 'Pet'
// pet.fly();
// ❌ 错误:Property 'swim' does not exist on type 'Pet'
// pet.swim();
// ✅ 正确:layEggs 是所有类型的共有方法
pet.layEggs();
// 需要使用类型守卫来访问特定方法
if ("fly" in pet) {
pet.fly(); // TypeScript 知道这里是 Bird
} else {
pet.swim(); // TypeScript 知道这里是 Fish
}
}类型检查示例
常见错误
typescript
// ❌ 错误:类型不匹配
type Status = "pending" | "completed";
let status: Status = "cancelled"; // Error: Type '"cancelled"' is not assignable to type 'Status'
// ❌ 错误:访问不存在的属性
type StringOrNumber = string | number;
let value: StringOrNumber = "hello";
console.log(value.length); // Error: Property 'length' does not exist on type 'number'
// ❌ 错误:未进行类型守卫
function process(value: string | number): void {
console.log(value.toUpperCase()); // Error: Property 'toUpperCase' does not exist on type 'number'
}正确写法
typescript
// ✅ 正确:使用有效的字面量值
type Status = "pending" | "completed" | "cancelled";
let status: Status = "pending"; // ✅ 正确
// ✅ 正确:使用类型守卫
type StringOrNumber = string | number;
let value: StringOrNumber = "hello";
if (typeof value === "string") {
console.log(value.length); // ✅ 正确:TypeScript 知道 value 是 string
} else {
console.log(value.toFixed(2)); // ✅ 正确:TypeScript 知道 value 是 number
}
// ✅ 正确:使用类型守卫处理
function process(value: string | number): void {
if (typeof value === "string") {
console.log(value.toUpperCase()); // ✅ 正确
} else {
console.log(value * 2); // ✅ 正确
}
}注意事项
提示
- 联合类型使用
|操作符,表示"或"的关系 - 使用可辨识联合(Discriminated Union)模式可以让类型推断更准确
- 类型守卫是使用联合类型的关键,确保类型安全
- 联合类型只能访问所有类型共有的成员,需要使用类型守卫访问特定成员
注意
- 在使用联合类型时,必须进行类型守卫才能访问特定类型的成员
- 联合类型会取所有类型的交集(交集类型),只能访问共有成员
- 字面量联合类型可以创建精确的值集合,提供更好的类型安全
- 避免创建过于复杂的联合类型,保持类型的可读性和可维护性
信息
联合类型在以下场景特别有用:
- API 响应可能返回不同的结构
- 处理可能为 null 或 undefined 的值(
string | null | undefined) - 定义状态机的状态(
"idle" | "loading" | "success" | "error") - 处理多种输入类型
- 实现多态行为
重要
- 联合类型是 TypeScript 类型系统的基础特性,理解联合类型对于学习类型守卫、类型断言等概念非常重要
- 使用联合类型时,务必进行适当的类型守卫,避免运行时错误
- 可辨识联合(Discriminated Union)是处理复杂联合类型的最佳实践
- 联合类型与交叉类型(Intersection Types)不同:联合类型是"或",交叉类型是"且"
联合类型与交叉类型
联合类型(|)和交叉类型(&)是 TypeScript 中两个重要的类型操作符:
typescript
// 联合类型:值可以是类型 A 或类型 B
type StringOrNumber = string | number;
// 交叉类型:值必须同时是类型 A 和类型 B
type NameAndAge = { name: string } & { age: number };
// 联合类型:只能访问共有成员
type A = { a: number; common: string };
type B = { b: number; common: string };
type AOrB = A | B;
let value: AOrB = { a: 1, common: "hello" };
// value.a; // ❌ 错误:不能访问,因为 B 没有 a
// value.common; // ✅ 正确:可以访问共有成员
// 交叉类型:可以访问所有成员
type AAndB = A & B;
let value2: AAndB = { a: 1, b: 2, common: "hello" };
value2.a; // ✅ 正确
value2.b; // ✅ 正确
value2.common; // ✅ 正确信息
关于交叉类型的详细内容,请参考交叉类型章节。