第 2 章:本质区别 —— 名义类型 vs 结构类型
在上一章中,我们讨论了传统"标准答案"的局限性。现在,让我们深入了解 interface 和 type 的本质区别:名义类型 vs 结构类型。
TypeScript 的结构类型系统
TypeScript 采用的是结构类型系统(Structural Type System),而不是名义类型系统(Nominal Type System)。这意味着两个类型的兼容性是由它们的结构决定的,而不是由它们的名称决定的。
让我们看一个例子:
interface User {
name: string;
age: number;
}
interface Customer {
name: string;
age: number;
}
const user: User = { name: "Alice", age: 30 };
const customer: Customer = user; // 这是可以的!在这个例子中,尽管是不同的接口,但由于它们具有相同的结构,TypeScript 认为它们是兼容的。
相比之下,在名义类型系统(如 Java)中,即使两个类有完全相同的属性和方法,它们也不能互相赋值,除非显式声明继承关系。
interface 是"开放契约"
interface 在 TypeScript 中被设计为"开放契约",这意味着它可以被多次声明并且会自动合并:
interface Logger {
log(message: string): void;
}
// 在另一个文件中扩展同一个接口
interface Logger {
warn(message: string): void;
}
// 最终效果相当于:
// interface Logger {
// log(message: string): void;
// warn(message: string): void;
// }
const logger: Logger = {
log(message) { console.log(message); },
warn(message) { console.warn(message); }
};这种特性使得我们可以扩展第三方库的类型定义,或者在一个大型项目中逐步增强接口的功能。
type 是"封闭别名"
与 interface 不同,type 创建的是一个"封闭别名",一旦定义就不能再扩展:
type User = {
name: string;
age: number;
};
// 这样做会报错!
// type User = {
// email: string;
// };type 更像是一种类型级别的变量,给复杂类型起了一个简短的名字。
实际应用中的选择策略
了解了这两种本质区别后,我们可以形成一个新的选择策略:
- 如果你需要定义一个可以被扩展的"契约",特别是在面向对象的设计中,使用
interface - 如果你只是想给复杂类型起个别名,或者定义联合类型、元组等,使用
type
示例:第三方库扩展
当你使用第三方库时,经常需要扩展库中定义的类型:
// 第三方库定义
interface Config {
host: string;
port: number;
}
// 你的项目想要添加额外配置项
interface Config {
timeout?: number;
retries?: number;
}这种情况非常适合使用 interface,因为它支持声明合并。
示例:内部数据结构
对于内部使用的数据结构,特别是 DTO(Data Transfer Object),通常更适合使用 type:
type UserDTO = {
id: number;
name: string;
email: string;
};
// 或者联合类型
type Status = 'pending' | 'approved' | 'rejected';小结
在本章中,我们深入探讨了 interface 和 type 的本质区别:
- TypeScript 采用结构类型系统,不关心类型的名称,只关心结构
interface是"开放契约",支持声明合并type是"封闭别名",强调不可变性
下一章,我们将探讨面向对象编程中如何更好地使用 interface,包括类的契约、继承体系等内容。
思考题: 你能想到哪些场景特别适合使用 interface 的声明合并特性?又有哪些场景更适合使用 type 的不可变性?