Skip to content

在 TypeScript 的类型系统中,联合类型 (|) 给了我们“或”的选择,允许一个值成为多种类型之一。而与之相对的,则是提供了“与”能力的交叉类型 (Intersection Types)。它允许我们将多个类型合并(或“交叉”)成一个单一的、拥有所有类型成员的新类型。

交叉类型是实现“组合优于继承”设计原则的强大工具,特别适合用于创建复杂的、由多个部分组合而成的类型。

什么是交叉类型?

交叉类型使用 & 运算符,将多个已存在的类型组合在一起。最终生成的类型将包含所有原始类型的所有属性。

其基本语法如下:

typescript
type TypeA = { a: string };
type TypeB = { b: number };

type CombinedType = TypeA & TypeB;

// CombinedType 现在等价于:
// {
//   a: string;
//   b: number;
// }

任何被声明为 CombinedType 的对象,都必须同时拥有 ab 两个属性,并符合其各自的类型要求。

让我们看一个更实际的例子。假设我们正在为一个 CRM 系统建模,一个客户的完整资料由“联系信息”和“业务信息”两部分组成:

typescript
type ContactInfo = {
  email: string;
  phone: string;
};

type BusinessInfo = {
  companyName: string;
  vatNumber: string;
};

// 使用交叉类型组合成一个完整的客户档案
type CustomerProfile = ContactInfo & BusinessInfo;

const customer: CustomerProfile = {
  email: "contact@example.com",
  phone: "123-456-7890",
  companyName: "Example Inc.",
  vatNumber: "VAT123",
};

通过 & 运算符,CustomerProfile 类型现在同时拥有了 ContactInfoBusinessInfo所有属性。这种方式清晰地表达了“一个完整的客户档案是由联系信息和业务信息共同组成的”这一业务逻辑。

当原始类型相交时

一个有趣且重要的知识点是:当原始类型(如 string, number)进行交叉时会发生什么?

typescript
type ImpossibleType = string & number;

ImpossibleType 的类型是 never。这是因为在类型系统中,一个值不可能同时string numbernever 类型正是用来表示这种永远不会发生的、不合逻辑的类型状态。这个特性在进行类型推导和确保类型安全时非常有用。

交叉类型 (&) vs. 接口扩展 (extends)

初学者可能会对 &extends 感到困惑,因为它们都能实现类型的扩展。但它们的语义和适用场景有着本质的区别。

特性interface extends ... (接口扩展)type ... & ... (交叉类型)
语义继承 (Inheritance)。表达一种 “is-a” (是一个) 的层次关系。例如,Admin extends User 意味着 Admin 是一种特殊的 User组合 (Composition)。表达一种 “has-a” (拥有) 的聚合关系,将多个独立的类型特征组合在一起。
使用场景构建清晰的对象层次结构,如 Square extends Shape将不同维度的功能“混入”(Mixin)到一个类型中,如 Draggable & Clickable
来源只能用于 interface 之间。可以用于 typeinterface 的任意组合。

简单来说:

  • 当你能说 “A 是一种 B” 时,使用 extends
  • 当你需要将多个独立的功能块组合成一个新的整体时,使用 &

实践应用:实现混入 (Mixin) 模式

交叉类型最强大的应用场景之一是实现混入 (Mixin) 模式。Mixin 是一种在不使用类继承的情况下,为对象添加可复用功能的设计模式。

假设我们有两个函数,它们各自为对象添加一种能力:一个添加时间戳,另一个添加日志功能。

typescript
type HasTimestamp = {
  timestamp: number;
};

type HasLogger = {
  log(message: string): void;
};

// 这个函数为一个对象添加时间戳能力
function withTimestamp<T>(obj: T): T & HasTimestamp {
  return { ...obj, timestamp: Date.now() };
}

// 这个函数为一个对象添加日志能力
function withLogger<T>(obj: T): T & HasLogger {
  return {
    ...obj,
    log: (message: string) => console.log(`[LOG] ${message}`),
  };
}

// 基础对象
const basicObject = {
  name: "My Component",
};

// 使用函数组合和交叉类型来构建一个功能丰富的对象
const composedObject = withLogger(withTimestamp(basicObject));

// 现在 composedObject 的类型是 { name: string } & HasTimestamp & HasLogger
console.log(composedObject.name);
console.log(composedObject.timestamp);
composedObject.log("Component initialized.");

在这个例子中,composedObject 的最终类型是 basicObject 的原始类型与 HasTimestampHasLogger 的交叉。TypeScript 能够完美地理解这个组合后的新类型,并为我们提供完整的类型检查和代码提示。这是一种极其灵活且类型安全的组合方式。

总结

交叉类型是 TypeScript 中实现“组合”思想的核心工具。它使我们能够以一种声明式的方式,将多个独立的类型合并成一个功能更强大的复合类型。

  • 它使用 & 运算符,将所有成员类型的属性聚合在一起。
  • 它与 extends 的继承关系不同,更侧重于功能的组合聚合
  • 在实现混入 (Mixin) 等高级设计模式时,交叉类型是不可或缺的强大工具。

当你需要构建一个由多个独立部分“拼装”而成的复杂类型时,请记住交叉类型。它将帮助你创建出结构清晰、精确且富有表现力的类型定义。

不知道说啥了很无语了