TypeScript 交叉类型与联合类型

在TypeScript的类型系统中,交叉类型(Intersection Types)和联合类型(Union Types) 是构建复杂类型表示的两大核心工具。它们提供了灵活的类型组合能力,让开发者能够精确描述各种数据结构。本文将深入探讨这两种类型的概念、区别以及实际应用场景。

一、联合类型(Union Types):"或"的关系

联合类型表示一个值可以是几个不同类型中的一个 ,使用 | 符号连接。

基本用法

ini 复制代码
// 变量可以是字符串或数字
type StringOrNumber = string | number;

let value: StringOrNumber;
value = "hello";   // 有效
value = 42;        // 有效
value = true;      // 错误:布尔值不是允许的类型

// 函数参数可以是三种字符串字面量之一
type Status = "pending" | "approved" | "rejected";

function setStatus(status: Status) {
  // ...
}

setStatus("approved");  // 有效
setStatus("deleted");   // 错误:不在允许的值中

类型守卫与联合类型

在使用联合类型时,TypeScript会限制只能访问所有类型共有的成员

scss 复制代码
interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

type Animal = Bird | Fish;

function animalAction(animal: Animal) {
  animal.layEggs();     // 有效:所有动物共有
  animal.fly();         // 错误:可能不是所有动物都有
  animal.swim();        // 错误:可能不是所有动物都有
}

要访问特定类型的成员,需要使用类型守卫

scss 复制代码
// 1. 使用类型断言
if ((animal as Bird).fly) {
  (animal as Bird).fly();
}

// 2. 使用in运算符
if ('fly' in animal) {
  animal.fly();
}

// 3. 用户自定义的类型守卫
function isBird(animal: Animal): animal is Bird {
  return (animal as Bird).fly !== undefined;
}

if (isBird(animal)) {
  animal.fly();
}

可辨识联合(Discriminated Unions)

当联合类型中的类型有共同的可辨识特征时,TypeScript能进行更精确的类型推断:

typescript 复制代码
type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "rectangle"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      // 此处shape自动被识别为圆
      return Math.PI * shape.radius ** 2;
    case "square":
      // 此处shape自动被识别为正方形
      return shape.side ** 2;
    case "rectangle":
      // 此处shape自动被识别为矩形
      return shape.width * shape.height;
  }
}

二、交叉类型(Intersection Types):"与"的关系

交叉类型表示一个类型同时具有多个类型的特性 ,使用 & 符号连接。

基本用法

typescript 复制代码
interface Person {
  name: string;
  age: number;
}

interface Employee {
  id: number;
  department: string;
}

// 合并后的类型具有两种类型的所有属性
type EmployeePerson = Person & Employee;

const john: EmployeePerson = {
  name: "John",
  age: 30,
  id: 123,
  department: "Engineering"
};

交叉类型的类型组合

交叉类型在组合多个类型时非常灵活:

typescript 复制代码
// 创建可序列化的错误类型
type SerializableError = Error & {
  toJSON(): string;
};

// 具有只读和可写特性的混合数组
type ReadonlyWritableArray<T> = ReadonlyArray<T> & Array<T>;

三、关键区别:联合类型 vs 交叉类型

特性 联合类型 交叉类型
符号 ` `
含义 "或"关系 "与"关系
可以是其中任意一个类型的值 必须同时满足所有类型的值
访问成员 只能访问共有成员 可以访问所有成员
类型检查 需要类型守卫确定具体类型 自动拥有所有特性
典型用途 函数参数多种可能、状态表示 组合多个接口、混合模式

理解区别的直观示例

ini 复制代码
type A = { x: number };
type B = { y: string };
type C = { z: boolean };

// 联合类型:具有 x 或 y 或 z
type Union = A | B | C;
const u: Union = { x: 1 };       // 有效
const u2: Union = { y: "hi" };   // 有效

// 交叉类型:同时具有 x, y 和 z
type Intersection = A & B & C;
const i: Intersection = {        // 必须包含所有属性
  x: 1,
  y: "hello",
  z: true
};

四、高级用法与实际应用场景

1. 函数重载简化

使用联合类型简化重载声明:

typescript 复制代码
// 传统重载写法
function getLength(obj: string): number;
function getLength(obj: any[]): number;
function getLength(obj: string | any[]): number {
  return obj.length;
}

// 联合类型简化版
type StringOrArray = string | any[];
function getLength(obj: StringOrArray): number {
  return obj.length;
}

2. 组合类型与条件类型

综合使用联合和交叉类型创建高级类型工具:

typescript 复制代码
// 提取所有函数的返回类型
type ReturnTypes<T> = 
  T extends (...args: any[]) => infer R ? R : never;

// 创建非空类型
type NonNullable<T> = T extends null | undefined ? never : T;

// 交叉类型的条件判断
type ExtractProps<T> = T extends object ? keyof T : never;

// 混合多个对象属性
type Merge<A, B> = A & B & { [K in keyof A & keyof B]?: never };

3. React Props类型

在React中结合使用两种类型:

typescript 复制代码
type ButtonProps = {
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

const Button: React.FC<ButtonProps> = ({ 
  primary, 
  size = 'medium', 
  ...props 
}) => {
  // ...
};

五、实用技巧与最佳实践

1. 类型缩减

TypeScript会自动进行类型缩减:

typescript 复制代码
// string & number 会自动缩减为 never
type Impossible = string & number; // => never

// boolean类型实际上是 true | false 的联合类型
type Boolean = true | false;

2. 联合类型的键访问

typescript 复制代码
type User = { name: string; age: number } | { username: string; email: string };

// keyof (A | B) 是 keyof A 和 keyof B 的交集
type UserKeys = keyof User; // => never (无共同键)

function safeGet<K extends keyof User>(user: User, key: K) {
  // 需要类型守卫确保安全
  if (key in user) {
    return user[key];
  }
  return undefined;
}

3. 使用类型别名提高可读性

css 复制代码
// 复杂的类型组合可以拆解为多个别名
type ApiResponse<T> = 
  | { status: "success"; data: T }
  | { status: "error"; message: string; code: number };

function handleResponse(response: ApiResponse<User>) {
  // ...
}

4. 组合类型时的性能考虑

当交叉或联合大量复杂类型时,TypeScript的类型检查可能会变慢。此时可以使用接口继承替代交叉类型:

typescript 复制代码
// 使用接口继承
interface Combined extends Person, Employee {
  // 可以添加额外字段
  role: string;
}

// 替代交叉类型
type Combined = Person & Employee & {
  role: string;
};

六、常见问题解决

联合类型误用为交叉类型

ini 复制代码
// 错误:以为可以接受同时具有两种接口的对象
type MyType = A | B;
const obj: MyType = { ...A, ...B }; // 可能错误,取决于类型定义

// 正确:使用交叉类型
type MyType = A & B;

处理可能的冲突字段

当交叉类型中的字段冲突时:

ini 复制代码
type A = { id: number };
type B = { id: string };

// 冲突字段会变为 never 类型
type Conflict = A & B;
const c: Conflict = {
  id: 5 // 错误:不能将number赋值给never
};

解决方案:使用类型映射处理冲突

less 复制代码
type MergeAvoidingConflicts<A, B> = {
  [K in keyof (A | B) as K extends keyof A ? (K extends keyof B ? never : K) : K]:
    K extends keyof A ? A[K] : K extends keyof B ? B[K] : never;
};

七、何时使用哪种类型

场景 推荐类型 原因
值可为多类型之一 联合类型(` `)
需要组合多个对象的属性 交叉类型(&) 合并类型特性
需要区分多种情况 联合类型 + 可辨识属性 类型守卫易用性
创建新类型扩展已有接口 交叉类型 无重复声明
描述状态机流转 联合类型 清晰表达互斥状态
混入多个类或对象 交叉类型 组合多个实现
函数参数多种可能 联合类型 灵活接受不同参数

实际开发建议

  1. 优先使用联合类型处理多种可能性场景
  2. 谨慎使用交叉类型组合复杂类型,防止意外合并
  3. 利用可辨识联合提高类型安全性
  4. 避免过度嵌套 - 复杂类型可拆分为多个别名
  5. 使用类型注释增强可读性// type: 用户身份或访客

交叉类型和联合类型是TypeScript类型系统的两大支柱,掌握它们的特性和适用场景,能让你的类型定义更加精确灵活。理解何时选择何种类型,是提升TypeScript编程水平的重要一步。

"TypeScript的类型系统就像一个拼图游戏,交叉类型提供连接件,联合类型提供多种选择,共同构建出完整的类型图景。" - TypeScript设计理念

相关推荐
ze_juejin1 小时前
Typescript中的继承示例
前端·typescript
夏天19951 小时前
TypeScript 一 泛型使用建议
typescript
一生躺平的仔2 小时前
TypeScript 初探:你的 JavaScript 进化伙伴
typescript
一生躺平的仔2 小时前
类型系统:给代码装上智能导航
typescript
RJiazhen8 小时前
项目级组件封装指南
前端·react.js·typescript
拾光拾趣录8 小时前
TypeScript 元组
typescript
阿珊和她的猫9 小时前
Axios 在 Vue3 项目中的使用:从安装到组件中的使用
前端·javascript·vue.js·typescript
叶 落12 小时前
三种语言写 MCP
java·python·ai·typescript·mcp
阿珊和她的猫1 天前
组件之间的双向绑定:v-model
前端·javascript·vue.js·typescript