TL;TD
- Interfaces 表示对象类型, Type aliases 可以表示所有类型。
- Interfaces 可以通过
extends
进行扩展, Type aliases 不可以。
- Type aliases 可以通过
&
模拟extends
的效果,性能上不如extends
。
- Interfaces 具有声明合并,可能会引起非预期行为。
- Type aliases 拥有隐式索引签名,Interfaces 没有。
在 TypeScript 中,我们可以使用
interface
和 type
关键字(类型别名)声明类型,像下面这样:interface Person { name: string; age: number; } type Point = { x: number; y: number; } type ID = number | string;
interface
和 type
的作用相似,何时使用 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
。当然,如果你清楚上述的差异,你可以自由选择。我的首选是 type
,type
足够灵活,可以满足大部分使用场景,如果有 extends
的需求,则使用 interface
。