编程全栈开发人员

描述 TypeScript 中的交叉类型 (Intersection Types) 机制。如何利用它们实现组合类型,与联合类型有什么区别,以及在属性的继承和兼容性方面有什么主要细节?

用 Hintsage AI 助手通过面试

回答。

交叉类型 (Intersection Types) 在 TypeScript 中允许创建组合类型,这些类型结合了所有源类型的属性和方法。这是构建灵活和可扩展数据结构的强大工具,而无需过度继承类。该构造通过在类型之间使用 & 运算符实现。

问题背景

TypeScript 从早期版本开始就支持联合类型 (|) 来表示“或”,但通常会出现描述具有许多来自不同独立接口或类型的属性的对象的任务。在这种情况下,使用交叉 (&)——对象必须满足所有接口的所有属性。

问题

其中一个主要复杂性是交叉类型中相同名称属性的冲突,它们的类型差异,以及如果合并具有私有或受保护字段的类时最终组合类型的正确性。此外,交叉类型与联合类型 (union types) 的混淆常常导致编译错误或处理对象的非明显错误。

解决方案

交叉类型聚合了所有合并类型中的属性,对于每个属性,如果名称匹配,则需要满足两个类型。可用于接口和类型别名。

interface A { foo: string; } interface B { bar: number; } type AB = A & B; const item: AB = { foo: "hello", bar: 123 }; // 正确

如果交叉的属性具有相同名称,则类型必须匹配,否则会出现错误:

interface X { val: string; } interface Y { val: number; } // type Z = X & Y; // 错误:val 不兼容

关键特性:

  • 运算符 & 结合了两个类型的所有属性(接口和类型别名)。
  • 如果属性在两个类型中都存在,最终类型就是它们的交集(它必须同时满足两个)。
  • 用于灵活结合功能,而无需创建新类或层次结构。

有陷阱的问题。

如果类型交叉而某些属性具有不兼容的类型会发生什么?(例如,一个属性是字符串,另一个是数字)

将出现编译错误,因为属性不可能同时是字符串和数字。

可以交叉具有私有或保护属性的类吗?

可以,但如果这些字段具有相同的名称且访问权限不同,则结果将无效,并且 TypeScript 将报错。

交叉类型与联合类型 (|) 有什么区别?

交叉 (A & B) 要求对象必须同时属于两种类型,而联合 (A | B) 要求对象至少属于其中一种类型。例如:

type U = A | B; // 可以是 foo 或 bar 或两者都有 type I = A & B; // 必须是两个属性

常见错误和反模式

  • 交叉不兼容类型或属性冲突具有不同类型将导致错误。
  • 滥用交叉以绕过严格类型检查会使代码复杂。

现实中的例子

负面案例

开发人员通过交叉多个不一致的接口创建类型;属性冲突,出现难以调试的类型错误。

优点: 快速整合多个类型的能力

缺点: 代码无法编译或包含隐性错误,调试困难

正面案例

将功能划分为独立接口,并通过交叉类型仔细将它们结合以形成最终的组合对象。

优点: 可扩展性,易于测试和扩展,严格的合同

缺点: 需要额外的工作来设计接口及其协调