Skip to content

交叉类型

概述

交叉类型(Intersection Types)是 TypeScript 中一个强大的类型特性,它允许将多个类型合并成一个类型,新类型必须同时满足所有被合并的类型。使用 & 操作符将多个类型组合在一起,表示"且"的关系。交叉类型让类型系统能够精确地组合多个类型的特性,创建更丰富、更具体的类型定义。交叉类型在混入(Mixin)模式、类型扩展、组合多个接口等场景中非常有用。

基本语法

交叉类型使用 & 操作符将多个类型组合:

typescript
// 基本语法:类型1 & 类型2 & 类型3
type NameAndAge = { name: string } & { age: number };
type Combined = TypeA & TypeB & TypeC;

基本用法

合并对象类型

最常见的交叉类型用法是合并多个对象类型:

typescript
// 定义基础类型
type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: string;
  department: string;
};

// 使用交叉类型合并
type EmployeePerson = Person & Employee;

// EmployeePerson 必须包含 Person 和 Employee 的所有属性
const employee: EmployeePerson = {
  name: "Alice",
  age: 30,
  employeeId: "E001",
  department: "Engineering"
};

// ❌ 错误:缺少必需属性
// const invalid: EmployeePerson = {
//   name: "Bob",
//   age: 25
//   // Error: Property 'employeeId' is missing
// };

合并多个接口

交叉类型可以合并多个接口:

typescript
// 定义多个接口
interface Flyable {
  fly(): void;
  maxAltitude: number;
}

interface Swimmable {
  swim(): void;
  maxDepth: number;
}

interface Walkable {
  walk(): void;
  speed: number;
}

// 使用交叉类型创建复合类型
type SuperCreature = Flyable & Swimmable & Walkable;

// 实现所有接口的方法和属性
const creature: SuperCreature = {
  fly() {
    console.log("Flying...");
  },
  maxAltitude: 10000,
  swim() {
    console.log("Swimming...");
  },
  maxDepth: 5000,
  walk() {
    console.log("Walking...");
  },
  speed: 10
};

creature.fly();   // ✅ 正确
creature.swim();  // ✅ 正确
creature.walk();  // ✅ 正确

合并函数类型

交叉类型也可以用于合并函数类型,但实际使用较少:

typescript
// 定义函数类型
type Increment = (x: number) => number;
type Decrement = (x: number) => number;

// 交叉类型:必须同时满足两个函数类型
// 实际上,这要求函数同时是 Increment 和 Decrement
// 这在实践中很少使用,因为一个函数通常只实现一种行为
type Counter = Increment & Decrement;

// 更常见的用法:合并对象中的函数属性
type Calculator = {
  add: (a: number, b: number) => number;
} & {
  subtract: (a: number, b: number) => number;
};

const calc: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

console.log(calc.add(5, 3));        // 8
console.log(calc.subtract(5, 3));   // 2

类型属性合并规则

同名属性的处理

当交叉类型中的多个类型有同名属性时,这些属性会被合并:

typescript
// 同名属性:相同类型会合并
type A = { x: number };
type B = { x: number };
type C = A & B;
// C 的 x 仍然是 number

// 同名属性:不同类型会交叉(通常结果是 never)
type D = { x: string };
type E = { x: number };
type F = D & E;
// F 的 x 是 never(因为 string & number = never)

// 验证
const test: F = {
  // x: "hello",  // ❌ 错误:不能赋值给 never
  // x: 42        // ❌ 错误:不能赋值给 never
};

// 同名属性:一个可选,一个必需
type G = { x?: number };
type H = { x: number };
type I = G & H;
// I 的 x 是 number(必需属性优先)

// 同名属性:都是可选
type J = { x?: string };
type K = { x?: number };
type L = J & K;
// L 的 x 是 string & number = never(但都是可选的)

实际应用:属性合并

typescript
// 定义基础配置
type BaseConfig = {
  host: string;
  port: number;
};

// 定义扩展配置
type ExtendedConfig = {
  timeout?: number;
  retries?: number;
};

// 定义认证配置
type AuthConfig = {
  apiKey?: string;
  token?: string;
};

// 合并所有配置
type FullConfig = BaseConfig & ExtendedConfig & AuthConfig;

// 使用合并后的配置
const config: FullConfig = {
  host: "api.example.com",
  port: 443,
  timeout: 5000,
  retries: 3,
  apiKey: "secret-key"
};

// 函数使用合并配置
function createClient(config: FullConfig): void {
  console.log(`Connecting to ${config.host}:${config.port}`);
  if (config.timeout) {
    console.log(`Timeout: ${config.timeout}ms`);
  }
  if (config.apiKey) {
    console.log("Using API key authentication");
  }
}

createClient(config);

使用示例

示例 1:混入(Mixin)模式

交叉类型非常适合实现混入模式,将多个功能组合到一个类型中:

typescript
// 定义可序列化接口
interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

// 定义可克隆接口
interface Cloneable {
  clone(): this;
}

// 定义可比较接口
interface Comparable<T> {
  compare(other: T): number;
}

// 使用交叉类型组合多个功能
type EnhancedObject<T> = T & Serializable & Cloneable & Comparable<T>;

// 实现混入类
class User {
  constructor(
    public name: string,
    public age: number
  ) {}

  greet(): void {
    console.log(`Hello, I'm ${this.name}`);
  }
}

// 创建增强的用户类型
type EnhancedUser = EnhancedObject<User>;

// 实现增强功能
class EnhancedUserImpl implements EnhancedUser {
  constructor(
    public name: string,
    public age: number
  ) {}

  greet(): void {
    console.log(`Hello, I'm ${this.name}`);
  }

  serialize(): string {
    return JSON.stringify({ name: this.name, age: this.age });
  }

  deserialize(data: string): void {
    const parsed = JSON.parse(data);
    this.name = parsed.name;
    this.age = parsed.age;
  }

  clone(): this {
    return new EnhancedUserImpl(this.name, this.age) as this;
  }

  compare(other: User): number {
    return this.age - other.age;
  }
}

// 使用示例
const user1 = new EnhancedUserImpl("Alice", 30);
const user2 = user1.clone();
const serialized = user1.serialize();
console.log(serialized);  // {"name":"Alice","age":30}

user1.deserialize('{"name":"Bob","age":25}');
console.log(user1.name);  // "Bob"

const comparison = user1.compare(user2);
console.log(comparison);  // -5 (user1 比 user2 小 5 岁)

示例 2:类型扩展和组合

交叉类型可以用于扩展和组合现有类型:

typescript
// 定义基础用户类型
type BaseUser = {
  id: number;
  name: string;
  email: string;
};

// 定义时间戳类型
type Timestamped = {
  createdAt: Date;
  updatedAt: Date;
};

// 定义可软删除类型
type SoftDeletable = {
  deletedAt?: Date;
  isDeleted: boolean;
};

// 定义可审计类型
type Auditable = {
  createdBy: string;
  updatedBy: string;
};

// 组合所有特性
type FullUser = BaseUser & Timestamped & SoftDeletable & Auditable;

// 创建用户对象
const user: FullUser = {
  id: 1,
  name: "John Doe",
  email: "john@example.com",
  createdAt: new Date("2024-01-01"),
  updatedAt: new Date("2024-01-15"),
  deletedAt: undefined,
  isDeleted: false,
  createdBy: "admin",
  updatedBy: "admin"
};

// 用户管理函数
function updateUser(user: FullUser, updates: Partial<BaseUser>): FullUser {
  return {
    ...user,
    ...updates,
    updatedAt: new Date(),
    updatedBy: "system"
  };
}

function deleteUser(user: FullUser): FullUser {
  return {
    ...user,
    deletedAt: new Date(),
    isDeleted: true,
    updatedAt: new Date(),
    updatedBy: "system"
  };
}

// 使用示例
const updatedUser = updateUser(user, { name: "Jane Doe" });
console.log(updatedUser.name);      // "Jane Doe"
console.log(updatedUser.updatedAt); // 新的更新时间

const deletedUser = deleteUser(updatedUser);
console.log(deletedUser.isDeleted); // true
console.log(deletedUser.deletedAt); // 删除时间

示例 3:事件系统类型组合

交叉类型可以用于组合事件系统的不同类型:

typescript
// 定义基础事件类型
type BaseEvent = {
  type: string;
  timestamp: number;
};

// 定义鼠标事件特性
type MouseEventProps = {
  x: number;
  y: number;
  button: "left" | "right" | "middle";
};

// 定义键盘事件特性
type KeyboardEventProps = {
  key: string;
  ctrlKey: boolean;
  shiftKey: boolean;
  altKey: boolean;
};

// 定义拖拽事件特性
type DragEventProps = {
  deltaX: number;
  deltaY: number;
  startX: number;
  startY: number;
};

// 组合不同类型的事件
type MouseEvent = BaseEvent & MouseEventProps & {
  type: "click" | "mousedown" | "mouseup" | "mousemove";
};

type KeyboardEvent = BaseEvent & KeyboardEventProps & {
  type: "keydown" | "keyup" | "keypress";
};

type DragEvent = BaseEvent & MouseEventProps & DragEventProps & {
  type: "dragstart" | "drag" | "dragend";
};

// 事件处理函数
function handleMouseEvent(event: MouseEvent): void {
  console.log(`${event.type} at (${event.x}, ${event.y}) with ${event.button} button`);
}

function handleKeyboardEvent(event: KeyboardEvent): void {
  const modifiers = [
    event.ctrlKey && "Ctrl",
    event.shiftKey && "Shift",
    event.altKey && "Alt"
  ].filter(Boolean).join("+");
  
  console.log(`${event.type}: ${modifiers ? modifiers + "+" : ""}${event.key}`);
}

function handleDragEvent(event: DragEvent): void {
  console.log(`${event.type}: from (${event.startX}, ${event.startY}) to (${event.x}, ${event.y})`);
  console.log(`Delta: (${event.deltaX}, ${event.deltaY})`);
}

// 使用示例
const clickEvent: MouseEvent = {
  type: "click",
  timestamp: Date.now(),
  x: 100,
  y: 200,
  button: "left"
};

const keyEvent: KeyboardEvent = {
  type: "keydown",
  timestamp: Date.now(),
  key: "Enter",
  ctrlKey: true,
  shiftKey: false,
  altKey: false
};

const dragEvent: DragEvent = {
  type: "drag",
  timestamp: Date.now(),
  x: 150,
  y: 250,
  button: "left",
  deltaX: 50,
  deltaY: 50,
  startX: 100,
  startY: 200
};

handleMouseEvent(clickEvent);    // "click at (100, 200) with left button"
handleKeyboardEvent(keyEvent);   // "keydown: Ctrl+Enter"
handleDragEvent(dragEvent);      // "drag: from (100, 200) to (150, 250)"

示例 4:API 请求和响应类型组合

交叉类型可以用于组合 API 请求和响应的不同类型:

typescript
// 定义基础请求类型
type BaseRequest = {
  method: "GET" | "POST" | "PUT" | "DELETE";
  url: string;
  headers?: Record<string, string>;
};

// 定义认证请求特性
type AuthenticatedRequest = {
  auth: {
    token: string;
    userId: string;
  };
};

// 定义分页请求特性
type PaginatedRequest = {
  pagination: {
    page: number;
    pageSize: number;
  };
};

// 定义查询参数特性
type QueryRequest = {
  query: Record<string, string | number | boolean>;
};

// 组合不同类型的请求
type GetRequest = BaseRequest & {
  method: "GET";
} & Partial<QueryRequest> & Partial<PaginatedRequest>;

type PostRequest = BaseRequest & AuthenticatedRequest & {
  method: "POST";
  body: unknown;
};

type PutRequest = BaseRequest & AuthenticatedRequest & {
  method: "PUT";
  body: unknown;
  id: string;
};

// 定义响应类型
type BaseResponse<T> = {
  status: number;
  statusText: string;
  data: T;
};

type PaginatedResponse<T> = BaseResponse<T[]> & {
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
  };
};

type ErrorResponse = {
  status: number;
  statusText: string;
  error: {
    code: string;
    message: string;
    details?: unknown;
  };
};

// API 客户端函数
async function fetchData<T>(request: GetRequest): Promise<BaseResponse<T>> {
  // 模拟 API 调用
  return {
    status: 200,
    statusText: "OK",
    data: {} as T
  };
}

async function createData<T>(
  request: PostRequest
): Promise<BaseResponse<T>> {
  // 模拟 API 调用
  return {
    status: 201,
    statusText: "Created",
    data: {} as T
  };
}

async function updateData<T>(
  request: PutRequest
): Promise<BaseResponse<T>> {
  // 模拟 API 调用
  return {
    status: 200,
    statusText: "OK",
    data: {} as T
  };
}

// 使用示例
const getUserRequest: GetRequest = {
  method: "GET",
  url: "/api/users",
  query: {
    role: "admin",
    active: true
  },
  pagination: {
    page: 1,
    pageSize: 10
  }
};

const createUserRequest: PostRequest = {
  method: "POST",
  url: "/api/users",
  auth: {
    token: "secret-token",
    userId: "user-123"
  },
  body: {
    name: "John Doe",
    email: "john@example.com"
  }
};

// 调用 API
fetchData(getUserRequest);
createData(createUserRequest);

交叉类型与联合类型的区别

交叉类型(&)和联合类型(|)是 TypeScript 中两个重要的类型操作符,它们有本质的区别:

概念区别

typescript
// 联合类型:值可以是类型 A 或类型 B(或的关系)
type StringOrNumber = string | number;
let value1: StringOrNumber = "hello";  // ✅ 可以是 string
let value2: StringOrNumber = 42;       // ✅ 可以是 number

// 交叉类型:值必须同时是类型 A 和类型 B(且的关系)
type NameAndAge = { name: string } & { age: number };
let person: NameAndAge = {
  name: "Alice",  // 必须有 name
  age: 30         // 必须有 age
};

成员访问区别

typescript
// 联合类型:只能访问所有类型共有的成员
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.b;      // ❌ 错误:不能访问,因为 A 没有 b
value.common;    // ✅ 正确:可以访问共有成员

// 交叉类型:可以访问所有类型的成员
type AAndB = A & B;
let value2: AAndB = { a: 1, b: 2, common: "hello" };
value2.a;        // ✅ 正确:可以访问 A 的成员
value2.b;        // ✅ 正确:可以访问 B 的成员
value2.common;   // ✅ 正确:可以访问共有成员

实际应用对比

typescript
// 联合类型:处理可能的不同类型
type ApiResponse = 
  | { status: "success"; data: User }
  | { status: "error"; message: string };

function handleResponse(response: ApiResponse): void {
  if (response.status === "success") {
    console.log(response.data);  // TypeScript 知道这里有 data
  } else {
    console.log(response.message);  // TypeScript 知道这里有 message
  }
}

// 交叉类型:组合多个特性
type Timestamped = { createdAt: Date };
type SoftDeletable = { deletedAt?: Date };
type Auditable = { updatedBy: string };

type FullEntity = Timestamped & SoftDeletable & Auditable;

function processEntity(entity: FullEntity): void {
  console.log(entity.createdAt);   // ✅ 可以访问所有属性
  console.log(entity.deletedAt);  // ✅ 可以访问所有属性
  console.log(entity.updatedBy);  // ✅ 可以访问所有属性
}

信息

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

类型检查示例

常见错误

typescript
// ❌ 错误:交叉类型中同名属性类型冲突
type A = { x: string };
type B = { x: number };
type C = A & B;
// C 的 x 是 never(string & number = never)

const c: C = {
  // x: "hello",  // ❌ 错误:不能赋值给 never
  // x: 42        // ❌ 错误:不能赋值给 never
};

// ❌ 错误:缺少交叉类型中的必需属性
type Person = { name: string };
type Employee = { employeeId: string };
type EmployeePerson = Person & Employee;

const person: EmployeePerson = {
  name: "John"
  // Error: Property 'employeeId' is missing
};

// ❌ 错误:交叉类型不能用于基础类型
type StringAndNumber = string & number;
// 结果是 never,因为一个值不能同时是 string 和 number

正确写法

typescript
// ✅ 正确:合并兼容的对象类型
type Person = { name: string; age: number };
type Employee = { employeeId: string; department: string };
type EmployeePerson = Person & Employee;

const employee: EmployeePerson = {
  name: "Alice",
  age: 30,
  employeeId: "E001",
  department: "Engineering"
};

// ✅ 正确:使用交叉类型扩展类型
type BaseConfig = { host: string; port: number };
type ExtendedConfig = BaseConfig & {
  timeout?: number;
  retries?: number;
};

const config: ExtendedConfig = {
  host: "api.example.com",
  port: 443,
  timeout: 5000
};

// ✅ 正确:组合多个接口特性
interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

type SuperCreature = Flyable & Swimmable;

const creature: SuperCreature = {
  fly() { console.log("Flying"); },
  swim() { console.log("Swimming"); }
};

注意事项

提示

  • 交叉类型使用 & 操作符,表示"且"的关系,必须同时满足所有类型
  • 交叉类型非常适合实现混入(Mixin)模式,组合多个功能的特性
  • 交叉类型可以访问所有被合并类型的成员,提供更丰富的类型信息
  • 使用交叉类型可以创建更具体、更精确的类型定义

注意

  • 当交叉类型中的多个类型有同名属性且类型不同时,结果类型可能是 never
  • 交叉类型要求值必须同时满足所有类型,确保所有必需属性都存在
  • 交叉类型不能用于基础类型的交叉(如 string & number),结果总是 never
  • 避免创建过于复杂的交叉类型,保持类型的可读性和可维护性

信息

交叉类型在以下场景特别有用:

  • 实现混入(Mixin)模式,组合多个功能的特性
  • 扩展和组合现有类型,添加新的属性或方法
  • 创建更具体、更精确的类型定义
  • 组合多个接口的特性
  • 实现类型增强和功能组合

重要

  • 交叉类型是 TypeScript 类型系统的基础特性,理解交叉类型对于学习高级类型特性非常重要
  • 交叉类型与联合类型是互补的概念:交叉类型是"且"(必须同时满足),联合类型是"或"(可以是其中之一)
  • 使用交叉类型时,注意同名属性的类型冲突问题
  • 交叉类型在混入模式和类型扩展中非常有用,是构建复杂类型系统的重要工具

相关链接

基于 VitePress 构建