TypeScript: TypeScript 交叉类型与高级组合

最后更新:2026-08-26

交叉类型(Intersection Types)用 & 把多个类型合并为一个——新类型同时拥有所有类型的全部特征。它是 TypeScript 组合类型的核心工具。

1. 交叉类型基础

(1) 基本语法

TYPESCRIPT
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

let person: Person = {
  name: "Charlie",
  age: 20
  // 必须同时有 name 和 age
};

(2) 与联合类型的对比

操作符 含义 类比
A & B(交叉) 同时满足 A 和 B AND(且)
`A B`(联合) 满足 A 或 B
TYPESCRIPT
type StringOrNumber = string | number;   // 联合:可以是其中之一
type StringAndNumber = string & number;   // 交叉:必须同时是两者 → never!

// 实际有意义的交叉——对象类型
type HasId = { id: number };
type HasName = { name: string };
type Entity = HasId & HasName;   // ✅ 同时有 id 和 name

(3) 多个类型交叉

TYPESCRIPT
type Timestamped = { createdAt: Date; updatedAt: Date };
type SoftDeletable = { deletedAt: Date | null };
type Auditable = { createdBy: string; updatedBy: string };

type FullEntity = Timestamped & SoftDeletable & Auditable;

let article: FullEntity = {
  createdAt: new Date(),
  updatedAt: new Date(),
  deletedAt: null,
  createdBy: "admin",
  updatedBy: "editor"
};

2. 交叉类型的合并规则

(1) 同名属性——类型取交集

当两个类型有同名属性但类型不同时,交叉结果的类型是两者的交集:

TYPESCRIPT
type A = { value: string | number };
type B = { value: string | boolean };
type C = A & B;

// C 的 value 类型 = (string | number) & (string | boolean) = string
let c: C = { value: "hello" };     // ✅ string 是交集
// let c2: C = { value: 42 };      // ❌ number 不在交集中
// let c3: C = { value: true };    // ❌ boolean 不在交集中

(2) 同名属性——不兼容类型产生 never

当两个类型同名属性完全不相交时,结果为 never——不可能存在这样的值:

TYPESCRIPT
type A = { id: string };
type B = { id: number };
type C = A & B;

// C 的 id 类型 = string & number = never
// 没有任何值能同时是 string 和 number
// let c: C = { id: "x" };  // ❌ 不能将 string 赋给 never
⚠️ 注意: TypeScript 不会在交叉产生 never 时主动报错——它只是让类型变不可用。这是交叉类型和 interface extends 的关键区别:extends 发现冲突会报错,& 交叉会默默产生 never。

(3) 函数类型的交叉

函数类型交叉时,参数取交集(通常产生 never):

TYPESCRIPT
type StringHandler = (value: string) => void;
type NumberHandler = (value: number) => void;
type MixedHandler = StringHandler & NumberHandler;

// MixedHandler 的参数 = string & number = never
// 实际上没有任何值能同时满足两个签名
💡 提示: 函数交叉在正常开发中极少使用。如果需要"可以处理多种类型"的函数,用联合类型(重载)而不是交叉。


3. 交叉类型与 interface extends 的区别

(1) 语法对比

TYPESCRIPT
// interface extends
interface Person {
  name: string;
}
interface Employee extends Person {
  employeeId: string;
}

// type 交叉
type Person2 = { name: string };
type Employee2 = Person2 & { employeeId: string };

(2) 冲突处理对比

场景 interface extends type 交叉 &
同名属性类型兼容 ✅ 子类型覆盖父类型 取交集
同名属性类型不兼容 ❌ 编译报错 默默产生 never
多继承 只能单继承 可以多交叉
声明合并 支持 不支持

(3) 选择建议

TYPESCRIPT
// 用 extends 的场景——需要编译时冲突检测
interface BaseConfig {
  host: string;
  port: number;
}

interface DevConfig extends BaseConfig {
  debug: boolean;    // ✅ 新增属性
  // host: number;   // ❌ 编译报错——与父类型 host: string 冲突
}

// 用交叉的场景——需要灵活组合多个类型
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithAudit = { createdBy: string; updatedBy: string };
type FullRecord = BaseConfig & WithTimestamps & WithAudit;
// 快速组合,不需要定义中间 interface

4. 类型组合的常见模式

(1) 模式一:能力混入(Mixin)

通过交叉类型给对象"混入"额外能力:

TYPESCRIPT
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithSoftDelete = { deletedAt: Date | null };
type WithAudit = { createdBy: string; updatedBy: string };

// 自由组合不同能力
type BaseEntity = WithId & WithTimestamps;
type FullEntity = WithId & WithTimestamps & WithSoftDelete & WithAudit;

interface Article extends BaseEntity {
  title: string;
  content: string;
}

let article: Article = {
  id: 1,
  createdAt: new Date(),
  updatedAt: new Date(),
  title: "TypeScript 入门",
  content: "TypeScript 是 JavaScript 的超集..."
};

(2) 模式二:条件组合

根据条件选择是否交叉某个类型:

TYPESCRIPT
type EntityWithOptional<T, TExtra> = T & Partial<TExtra>;

interface User {
  id: number;
  name: string;
}

interface UserProfile {
  avatar: string;
  bio: string;
}

// User + 可选的 Profile
type UserWithOptionalProfile = EntityWithOptional<User, UserProfile>;
// { id: number; name: string; avatar?: string; bio?: string }

(3) 模式三:品牌类型(Branded Types)

用交叉类型给基本类型"打标签",防止混用:

TYPESCRIPT
type USD = number & { __brand: "USD" };
type EUR = number & { __brand: "EUR" };

function createUSD(amount: number): USD {
  return amount as USD;
}

function createEUR(amount: number): EUR {
  return amount as EUR;
}

let price: USD = createUSD(100);
let cost: EUR = createEUR(80);

// price = cost;          // ❌ EUR 不能赋值给 USD
// price + cost;          // ❌ 不能混合计算

function addUSD(a: USD, b: USD): USD {
  return (a + b) as USD;  // 同币种可以计算
}

let total = addUSD(price, createUSD(50));
console.log(total);  // 150

▶ 示例:类型安全的配置组合

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

// 开发环境额外配置
type DevConfig = BaseConfig & {
  debug: true;
  mockApi: boolean;
};

// 生产环境额外配置
type ProdConfig = BaseConfig & {
  debug: false;
  ssl: boolean;
  maxConnections: number;
};

function createDevConfig(): DevConfig {
  return { host: "localhost", port: 3000, debug: true, mockApi: true };
}

function createProdConfig(): ProdConfig {
  return { host: "api.example.com", port: 443, debug: false, ssl: true, maxConnections: 100 };
}

let dev = createDevConfig();
let prod = createProdConfig();

console.log(`开发:${dev.host}:${dev.port} (mock: ${dev.mockApi})`);
console.log(`生产:${prod.host}:${prod.port} (ssl: ${prod.ssl})`);
▶ 试一试

输出:

TEXT 📖 仅展示
开发:localhost:3000 (mock: true)
生产:api.example.com:443 (ssl: true)

5. 交叉类型的高级技巧

(1) 递归类型变换

TYPESCRIPT
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object
    ? T[K] extends Array<any>
      ? T[K]
      : DeepPartial<T[K]>
    : T[K];
};

interface Config {
  server: { host: string; port: number };
  database: { url: string; pool: { min: number; max: number } };
}

type PartialConfig = DeepPartial<Config>;
let cfg: PartialConfig = {};  // 所有层级都可选

(2) 泛型函数中的交叉

TYPESCRIPT
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

let person = merge({ name: "Charlie" }, { age: 20 });
// person 类型:{ name: string } & { age: number }

(3) 条件类型中的交叉

TYPESCRIPT
type AddTimestamps<T> = T & { createdAt: Date; updatedAt: Date };

interface User {
  name: string;
  email: string;
}

type TimestampedUser = AddTimestamps<User>;
// { name: string; email: string; createdAt: Date; updatedAt: Date }

❓ 常见问题

Q 交叉类型产生 never 怎么排查?
A 逐步去掉交叉成员,看哪两个类型冲突。常见原因:同名属性类型不兼容(如 string & number)。建议用 interface extends 代替交叉——extends 在冲突时直接报错,更容易定位问题。
Q 交叉类型能替代继承吗?
A 大部分场景可以,但不完全等价。interface extends 有编译时冲突检测、声明合并支持、class implements 支持。交叉类型更灵活但没有冲突检测。对象类型组合推荐 extends,简单快速组合用交叉。
Q 品牌类型在实际开发中有用吗?
A 在需要区分"结构相同但语义不同"的类型时非常有用。典型场景:货币(USD vs EUR)、ID(UserId vs OrderId)、度量单位(Meters vs Feet)。品牌类型防止意外混用,编译时就能捕获"把欧元当美元"这类错误。
Q 交叉类型和联合类型能组合使用吗?
A 可以。type T = (A & B) | (C & D) 表示"要么同时满足A和B,要么同时满足C和D"。括号决定优先级——& 的优先级高于 |,所以 A & B | C & D 等于 (A & B) | (C & D)

📖 小节

📝 作业

  1. 基础题(难度⭐):定义 WithIdWithTimestamps 两个类型,用交叉类型组合为 BaseEntity,创建一个满足 BaseEntity 的对象。
  2. 进阶题(难度⭐⭐):实现品牌类型 UserId = number & { __brand: "UserId" }OrderId = number & { __brand: "OrderId" },写函数分别创建两种ID,验证它们不能互相赋值。
  3. 挑战题(难度⭐⭐⭐):实现 Overwrite<T, U> 工具类型——用 U 的属性覆盖 T 中同名属性,不同名属性保留。提示:用映射类型 + 交叉类型。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏