Interfaces 和 Types 的区别
Interfaces 和 Types 的区别

Interfaces 和 Types 的区别

Published
Aug 1, 2023
Author

TL;TD

  • Interfaces 表示对象类型, Type aliases 可以表示所有类型。
  • Interfaces 可以通过 extends 进行扩展, Type aliases 不可以。
  • Type aliases 可以通过 & 模拟 extends 的效果,性能上不如 extends
  • Interfaces 具有声明合并,可能会引起非预期行为。
  • Type aliases 拥有隐式索引签名,Interfaces 没有。
 

 
在 TypeScript 中,我们可以使用 interfacetype 关键字(类型别名)声明类型,像下面这样:
interface Person { name: string; age: number; } type Point = { x: number; y: number; } type ID = number | string;
interfacetype 的作用相似,何时使用 interface,何时使用 type 这需要我们了解二者的差别。

Type aliases 表示任何类型,Interfaces 表示对象类型

interface 可以用来定义对象类型,并且允许通过继承来创建类型:
interface WithId { id: string; } interface User extends WithId { name: string; }
另外也可以使用类型别名,type 关键字可以表示 TypeScript 中的任何类型,不仅仅只是对象类型。
// 例如:ID 类型允许 string 或 number type ID = string | number; // 也可以用表达对象类型 type User = { id: ID; name: string } // 类似 extends 的效果 type UserWithAge = User & { age: number };
通常这是引起争论的点,既然都能声明对象类型,那么该用谁呢?
如果你使用相互继承的对象,使用 interface 更好,虽然使用 & 可以达到类似的效果,旦不利于性能优化。当使用 extends 扩展 Interfaces 时,TypeScript 可以通过名称缓存该 interface,可以提升类型检查的速度。对于使用 & 的交集类型,无法做到这一点,这就意味着每次都需要重新计算。当项目中的 Interfaces 越多,这种优化的好处就体现的越明显。

Interfaces 会声明合并,Type aliases 不会

当同一作用域中有两个同名的 interfaces 时,它们会进行合并。
interface Mammal { genus: string } interface Mammal { breed?: string } const animal: Mammal = { genus: "1234", // Error: Type 'number' is not assignable to type 'string'. breed: 1 }
类型别名不会,它们只会发生冲突。
// Error: Duplicate identifier 'Reptile'. type Reptile = { genus: string } // Error: Duplicate identifier 'Reptile'. type Reptile = { breed?: string }
声明合并是必要的语言功能,例如向 Array 的原型添加方法,这将会非常有用。但是如果不太清楚声明合并,这可能会让你非常困惑。如果想避免这种情况,可以打开 ESLint 的 no-redeclare 规则。

Type aliases 的隐式索引签名(Index Signatures)

📖
了解更多关于 Index Signatures
Type aliases 具有隐式的索引签名,Interfaces 是没有的,看下面的例子:
interface Point { x: number; y: number; } const point: Point = { x: 1, y: 1, } type RecordType = Record<string, number>; // Error: Type 'Point' is not assignable to type 'RecordType'. // Index signature for type 'string' is missing in type 'Point'. const point1: RecordType = point;
问题在于,使用 interface 声明的类型在后续可能会进行扩展,例如先前提到的声明合并,有可能添加了不符合键为 string 值为 number 的属性。可以通过显式添加索引签名来解决:
interface Point { x: number; y: number; [index: string]: number; }
或者改用 type
type Point = { x: number; y: number; }

结论

Type aliases 和 Interfaces 的差别足够小,大家可以根据喜好自行选择。官方的建议是默认使用 interface,仅在需要时使用 type。当然,如果你清楚上述的差异,你可以自由选择。我的首选是 typetype 足够灵活,可以满足大部分使用场景,如果有 extends 的需求,则使用 interface

参考链接