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