Skip to content

联合类型

概述

联合类型(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;   // ✅ 正确

信息

关于交叉类型的详细内容,请参考交叉类型章节。

相关链接

基于 VitePress 构建