与许多面向对象语言不同,TypeScript 实现了结构类型(鸭子类型):如果一个对象具有所有必需的属性,那么它被认为与该类型兼容,无论是否显式声明为该类型。
这种灵活性有时会导致意外接受与类型匹配的对象,即使它们实际上没有逻辑上的关联。这在处理复杂数据模型时是危险的,因为结构可能是偶然匹配的。
始终正确结构化对象类型,最小化不同实体结构的匹配,对于关键情况使用额外的属性或符号来“命名”类型。
代码示例:
interface Point { x: number; y: number; } interface Pixel { x: number; y: number; } function drawPoint(p: Point) { console.log(p.x, p.y); } const pixel: Pixel = { x: 1, y: 2 }; drawPoint(pixel); // OK,类型在结构上兼容
关键特点:
如果两个接口的结构相同,是否意味着它们是完全互换的?
可能从结构上是互换的,但在程序逻辑上不是。这在编译器级别是允许的,但可能导致逻辑错误(例如,上述的 Point 和 Pixel)。
是否可以禁止某种类型的结构兼容性?
完全不可以,但可以添加唯一属性(例如,使用符号):
interface Brand { _brand: unique symbol; }
现在,另一个对象不能在没有相同唯一符号的情况下模拟该类型。
结构类型与命名类型有什么不同?
结构类型——基于结构的存在,命名类型——基于特定命名空间中的类型名匹配。在 TypeScript 中,默认为结构类型。
在一些实体中,无意间字段重叠(例如,User 和 Admin 作为 {id: number, name: string}),在处理 API 合同时导致混淆。
优点:
缺点:
使用唯一的“标签”符号和非标准字段来区分具有相同结构但在语义上不同的类型。
优点:
缺点: