TypeScript 交叉类型(Intersection Types)使用详解与冲突处理

作者:终端旅客

时间:2026-10-30

浏览:184

本文深入解析 TypeScript 中的交叉类型(Intersection Types),通过 & 运算符合并多个类型。文章涵盖对象字面量验证差异、接口与 type 的交集求法、模拟继承机制,重点讨论了属性类型冲突时生成 never 类型的场景,以及函数类型取交集时参数取并集的特殊规则,帮助开发者规避类型定义陷阱。

文章目录

在 TypeScript 类型系统中,交叉类型(Intersection Types)允许将多个类型合并为一个单一类型。这种机制通过 & 符号实现,旨在创建同时具备所有参与类型特征的复合类型。

type A = string & number; // never, 一般我们都不会把交集用在基本类型,而是用在对象上
type 有右手的人 = {
  right: string;
};
type C = 有左手的人 | 有右手的人;
type D = 有左手的人 & 有右手的人;
const d: D = {
  left: 'yes', // 会报错,缺少right
};

在 TypeScript 类型系统中,交叉类型(Intersection Types)允许将多个类型合并为一个单一类型。这种机制通过 & 符号实现,旨在创建同时具备所有参与类型特征的复合类型。

为了直观理解交叉类型的行为,我们可以考察一个关于“左手”与“右手”属性的示例。当尝试定义一个既拥有左手又拥有右手的类型时,如果两个源类型中没有共同的属性交集,或者属性类型存在根本性冲突,TypeScript 编译器会如何处理这种情况?

// 场景一. 直接初始化赋值
type 有左手的人 = {
  left: string;
};
const a: 有左手的人 = {
  left: '一米八',
  right: '一米五', // 这么写会报错
};
// 场景二. 先声明好对象,再赋值
type 有左手的人 = {
  left: string;
};
const b = {
  left: '一米八',
  right: '一米五',
};
const a: 有左手的人 = b; // 这么写不报错

通过上述示例可以看出,交叉类型并非总是能生成有效的对象类型。当参与交叉的类型之间没有任何共同属性,或者属性类型互斥时,结果可能是一个 never 类型,这意味着该类型在逻辑上无法实例化任何值。

在实际编码场景中,直接对象字面量声明会触发严格的属性匹配验证,而先声明变量再赋值的间接方式可能会绕过这种即时验证。因此,前者会因缺少必需属性而报错,后者则不会立即报错,这体现了 TypeScript 类型检查在不同赋值方式下的差异。

除了 type 别名,interface 同样支持求交集操作。通过将多个接口使用 & 符号连接,可以创建一个包含所有接口成员的新类型。这种方式常用于组合不同的功能模块或数据模型,确保新类型具备所有源接口的属性。

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}
type ColorfulCircle = Colorful & Circle;
const CC: ColorfulCircle = {
  color: 'red',
  radius: 11,
};

利用 & 运算符可以实现类似面向对象中继承的效果。通过交叉类型,可以将基础类型(如 Person)与扩展属性(如 User 特有的 id 和 email)合并,从而生成一个包含所有属性的复合类型。这种方式比传统的 class 继承更灵活,适用于函数式编程风格。

type Person = {
  name: string;
  age: number;
};
type User = Person & {
  id: number;
  email: string;
};
const u: User = {
  name: 'Jack',
  age: 18,
  id: 1,
  email: 'Jack@alibaba.com',
};

在使用 & 进行类型交叉时,如果两个源类型包含同名但类型不兼容的属性,就会发生属性冲突。此时,TypeScript 会尝试寻找这两个类型的交集,如果找不到兼容的类型,结果属性将被推断为 never。

当发生属性冲突时,交叉类型会尝试求交集。如果源类型的属性类型完全互斥(例如一个是 string,另一个是 number),最终该属性的类型将变为 never。这意味着在实例化对象时,必须提供一个 never 类型的值,这在逻辑上是不可能的,从而导致类型错误。

// 场景一
type Person = {
  name: string;
  age: number;
  id: string;
};
type User = Person & {
  id: number;
  email: string;
};
const u: User = {
  name: 'Jack',
  age: 18,
  id: 1 as never, // 这个变成了never,且必须存在
  email: 'Jack@alibaba.com',
};
// 场景二 User是never, 这是一种特殊的场景
type Person = {
  id: 'A';
  name: string;
  age: number;
};
type User = Person & {
  id: 'B';
  email: string;
};

当 interface 遇到属性冲突时,编译器会直接报错,提示类型不兼容。这与 type 的行为不同,type 在定义时可能不会立即报错,而是生成一个包含 never 属性的复杂类型,直到实际使用时才暴露问题。

interface Person {
  id: string;
  name: string;
}
// 会报错 接口“User”错误扩展接口“Person”。属性“id”的类型不兼容。不能将类型“number”分配给类型“string”。
interface User extends Person {
  id: number;
  email: string;
}

由此可见,在编写可扩展的类型时,interface 更为合适,因为它会在类型定义阶段就发现逻辑错误。如果使用 type 且定义不合逻辑,编译器只会生成一个包含 never 属性的类型,问题往往在后续使用时才会被发现,增加了调试难度。

交叉类型不仅适用于对象和接口,还可以应用于函数类型。对两个函数类型进行交叉运算,会生成一个新的函数类型,该函数需要同时满足所有源函数的签名要求。

两个函数类型求交集时,最终得到的函数其参数类型将是所有源函数参数类型的并集。这意味着新函数必须能够接受任何源函数所接受的参数类型。这一结论将在后续章节详细展开,此处先展示基本结果。

type A = {
  method: (a: number) => void;
};
type B = {
  method: (a: string) => void;
} & A;
const b: B = {
  method(a) {
    a; // number | string
  },
};

交叉类型主要用于处理具有共同部分的类型 A 和 B。如果 A 和 B 之间没有交集,或者属性类型互斥,结果可能会得到 never 类型,或者生成的对象中某些属性类型为 never。

交叉类型常用于有交集的类型 A,B。如果A,B无交集可能会得到never,也有可能是属性为never。

以上就是TypeScript 交叉类型使用方法示例总结的详细内容,更多关于TypeScript 交叉类型的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章