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)。📖 小节
- 交叉类型
A & B合并多个类型的全部特征——新类型必须同时满足所有成员 - 同名属性类型取交集,不兼容类型产生 never——TypeScript 不会主动报错
- interface extends 有冲突检测,type 交叉更灵活——根据场景选择
- 常见组合模式:能力混入(Mixin)、条件组合、品牌类型(Branded Types)
- 品牌类型用
& { __brand: "标签" }防止语义不同但结构相同的类型混用
📝 作业
- 基础题(难度⭐):定义
WithId和WithTimestamps两个类型,用交叉类型组合为BaseEntity,创建一个满足BaseEntity的对象。 - 进阶题(难度⭐⭐):实现品牌类型
UserId = number & { __brand: "UserId" }和OrderId = number & { __brand: "OrderId" },写函数分别创建两种ID,验证它们不能互相赋值。 - 挑战题(难度⭐⭐⭐):实现
Overwrite<T, U>工具类型——用 U 的属性覆盖 T 中同名属性,不同名属性保留。提示:用映射类型 + 交叉类型。