TypeScript 中 Interface 与 Type 的区别?该用哪个比较好?
接口 与 类型别名 的异同点
相同点
1. 都可以描述对象或函数<br />
// 接口
interface Sister {
name: string;
age: number;
}
interface SetSister {
(name: string, age: number): void;
}
// 类型别名
type Sister = {
name: string;
age: number;
};
type SetSister = (name: string, age: number) => void;
2. 都可以扩展
interface
和 type
可以混合扩展,也就是说 interface
可以扩展 type
,type
也可以扩展 interface
。
但需要注意的是,接口的扩展是继承( extends
)。类型别名的扩展就是交叉类型(通过 &
实现)
// 接口
interface SisterAn {
name: string;
}
// 类型别名
type SisterRan = {
age: number;
}
// 接口扩展接口
interface Sister extends SisterAn {
age: number;
}
// 类型别名扩展类型别名
type SisterPro = SisterRan & {
name: string;
}
// 接口扩展类型别名
interface Sister extends SisterRan {
name: string;
}
// 类型别名扩展接口
type SisterPro = SisterAn & {
age: number;
}
区别
官方 中这样介绍两者的区别:
Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an
interface
are available intype
, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
意思就是说几乎接口的所有特性都可以通过类型别名来实现,主要区别在于:
1. 不同的声明范围
与接口不同,可以为任意的类型创建类型别名
类型别名的右边可以是任何类型,包括基本类型、元祖、类型表达式( &
或 |
等);而在接口声明中,右边必须为变量结构。例如,下面的类型别名就不能转换成接口
type Name = string
type Text = string | { text: string };
type Coordinates = [number, number];
2. 不同的扩展形式
接口是通过继承的方式来扩展,类型别名是通过 &
来扩展
// 接口扩展
interface SisterAn {
name: string;
}
interface Sister extends SisterAn {
age: number;
}
// 类型别名扩展
type SisterRan = {
age: number;
}
type SisterPro = SisterRan & {
name: string;
}
这里需要注意的是,接口扩展时,typescript 会检查扩展的接口是否可以赋值给被扩展的接口
// 接口扩展
interface SisterAn {
name: string;
age: string
}
interface Sister extends SisterAn {
name: string;
age: number;
}
// 报错:
// Interface 'Sister' incorrectly extends interface 'SisterAn'.
// Types of property 'age' are incompatible.
// Type 'number' is not assignable to type 'string'
但使用交集类型时就不会出现这种情况
// 类型别名扩展
type SisterRan = {
name: string;
age: string;
}
type SisterPro = SisterRan & {
name: string;
age: number;
}
类型别名扩展时,typescript 将尽其所能把扩展和被扩展的类型组合在一起,而不会抛出编译时错误
3. 不同的重复定义表现形式
接口可以定义多次,多次的声明会自动合并
interface Sister {
name: string;
}
interface Sister {
age: number;
}
const sisterAn: Sister = {
name: 'sisterAn'
}
// 报错:Property 'age' is missing in type '{ name: string; }' but required in type 'Sister'
const sisterRan: Sister = {
name: 'sisterRan',
age: 12
}
// 正确
`
`
但是类型别名如果定义多次,会报错
type Sister = { // Duplicate identifier 'Sister'
name: string;
}
type Sister = { // Duplicate identifier 'Sister'
age: number;
}
如何选择 Interface 、 Type
虽然 官方 中说几乎接口的所有特性都可以通过类型别名来实现,但建议优先选择接口,接口满足不了再使用类型别名,在 typescript 官网 Preferring Interfaces Over Intersections 有说明,具体内容如下:
大多数时候,对象类型的简单类型别名的作用与接口非常相似
interface Foo { prop: string }
type Bar = { prop: string };
但是,一旦你需要组合两个或多个类型来实现其他类型时,你就可以选择使用接口扩展这些类型,或者使用类型别名将它们交叉在一个中(交叉类型),这就是差异开始的时候。
- 接口创建一个单一的平面对象类型来检测属性冲突,这通常很重要! 而交叉类型只是递归的进行属性合并,在某种情况下可能产生
never
类型- 接口也始终显示得更好,而交叉类型做为其他交叉类型的一部分时,直观上表现不出来,还是会认为是不同基本类型的组合。
- 接口之间的类型关系会被缓存,而交叉类型会被看成组合起来的一个整体。
- 最后一个值得注意的区别是,在检查到目标类型之前会先检查每一个组分。
出于这个原因,建议使用接口/扩展扩展类型而不是创建交叉类型。
type Foo = Bar & Baz & {
someProp: string;
}
interface Foo extends Bar, Baz {
someProp: string;
}
简单的说,接口更加符合 JavaScript 对象的工作方式,简单的说明下,当出现属性冲突时:
// 接口扩展
interface Sister {
sex: number;
}
interface SisterAn extends Sister {
sex: string;
}
// index.ts(5,11): error TS2430: Interface 'SisterAn' incorrectly extends interface 'Sister'.
// Types of property 'sex' are incompatible.
// Type 'string' is not assignable to type 'number'.
// 交叉类型
type Sister1 = {
sex: number;
}
type Sister2 = {
sex: string;
}
type SisterAn = Sister1 & Sister2;
// 不报错,此时的 SisterAn 是一个'number & string'类型,也就是 never