接口和类型别名很是类似,在大多状况下两者能够互换。在写TS的时候,想必你们都问过本身这个问题,我到底应该用哪一个呢?但愿看完本文会给你一个答案。知道何时应该用哪一个,首先应该了解两者之间的相同点和不一样点,再作出选择。git
interface Point { x: number y: number } interface SetPoint { (x: number, y: number): void; }
type Point = { x: number; y: number; }; type SetPoint = (x: number, y: number) => void;
二者的扩展方式不一样,但并不互斥。接口能够扩展类型别名,同理,类型别名也能够扩展接口。github
接口的扩展就是继承,经过 extends
来实现。类型别名的扩展就是交叉类型,经过 &
来实现。缓存
// 接口扩展接口 interface PointX { x: number } interface Point extends PointX { y: number }
// 类型别名扩展类型别名 type PointX = { x: number } type Point = PointX & { y: number }
// 接口扩展类型别名 type PointX = { x: number } interface Point extends PointX { y: number }
// 类型别名扩展接口 interface PointX { x: number } type Point = PointX & { y: number }
类型别名的右边能够是任何类型,包括基本类型、元祖、类型表达式(&
或|
等类型运算符);而在接口声明中,右边必须为结构。例如,下面的类型别名就不能转换成接口:ide
type A = number type B = A | string
扩展接口时,TS将检查扩展的接口是否能够赋值给被扩展的接口。举例以下:函数
interface A { good(x: number): string, bad(x: number): string } interface B extends A { good(x: string | number) : string, bad(x: number): number // Interface 'B' incorrectly extends interface 'A'. // Types of property 'bad' are incompatible. // Type '(x: number) => number' is not assignable to type '(x: number) => string'. // Type 'number' is not assignable to type 'string'. }
但使用交集类型时则不会出现这种状况。咱们将上述代码中的接口改写成类型别名,把 extends
换成交集运算符 &
,TS将尽其所能把扩展和被扩展的类型组合在一块儿,而不会抛出编译时错误。ui
type A = { good(x: number): string, bad(x: number): string } type B = A & { good(x: string | number) : string, bad(x: number): number }
接口能够定义屡次,屡次的声明会合并。可是类型别名若是定义屡次,会报错。code
interface Point { x: number } interface Point { y: number } const point: Point = {x:1} // Property 'y' is missing in type '{ x: number; }' but required in type 'Point'. const point: Point = {x:1, y:1} // 正确
type Point = { x: number // Duplicate identifier 'A'. } type Point = { y: number // Duplicate identifier 'A'. }
若是接口和类型别名都能知足的状况下,到底应该用哪一个是咱们关心的问题。感受哪一个均可以,可是强烈建议你们只要能用接口实现的就优先使用接口,接口知足不了的再用类型别名。orm
为何会这么建议呢?其实在TS的wiki中有说明。具体的文章地址在这里。对象
如下是Preferring Interfaces Over Intersections
的译文:继承
大多数时候,对于声明一个对象,类型别名和接口表现的很类似。
interface Foo { prop: string } type Bar = { prop: string };然而,当你须要经过组合两个或者两个以上的类型实现其余类型时,能够选择使用接口来扩展类型,也能够经过交叉类型(使用
&
创造出来的类型)来完成,这就是两者开始有区别的时候了。
- 接口会建立一个单一扁平对象类型来检测属性冲突,当有属性冲突时会提示,而交叉类型只是递归的进行属性合并,在某种状况下可能产生
never
类型- 接口一般表现的更好,而交叉类型作为其余交叉类型的一部分时,直观上表现不出来,仍是会认为是不一样基本类型的组合
- 接口之间的继承关系会缓存,而交叉类型会被当作组合起来的一个总体
- 在检查一个目标交叉类型时,在检查到目标类型以前会先检查每个组分
上述的几个区别从字面上理解仍是有些绕,下面经过具体的列子来讲明。
interface Point1 { x: number } interface Point extends Point1 { x: string // Interface 'Point' incorrectly extends interface 'Point1'. // Types of property 'x' are incompatible. // Type 'string' is not assignable to type 'number'. }
type Point1 = { x: number } type Point2 = { x: string } type Point = Point1 & Point2 // 这时的Point是一个'number & string'类型,也就是never
从上述代码能够看出,接口继承同名属性不知足定义会报错,而相交类型就是简单的合并,最后产生了 number & string
类型,能够解释译文中的第一点不一样,其实也就是咱们在不一样点模块中介绍的扩展时表现不一样。
再来看下面例子:
interface PointX { x: number } interface PointY { y: number } interface PointZ { z: number } interface PointXY extends PointX, PointY { } interface Point extends PointXY, PointZ { } const point: Point = {x: 1, y: 1} // Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point'
type PointX = { x: number } type PointY = { y: number } type PointZ = { z: number } type PointXY = PointX & PointY type Point = PointXY & PointZ const point: Point = {x: 1, y: 1} // Type '{ x: number; y: number; }' is not assignable to type 'Point'. // Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point3'.
从报错中能够看出,当使用接口时,报错会准肯定位到Point。
可是使用交叉类型时,虽然咱们的 Point
交叉类型是 PointXY & PointZ
, 可是在报错的时候定位并不在 Point
中,而是在 Point3
中,即便咱们的 Point
类型并无直接引用 Point3
类型。
若是咱们把鼠标放在交叉类型 Point
类型上,提示的也是 type Point = PointX & PointY & PointZ
,而不是 PointXY & PointZ
。
这个例子能够同时解释译文中第二个和最后一个不一样点。
有的同窗可能会问,若是我不须要组合只是单纯的定义类型的时候,是否是就能够随便用了。可是为了代码的可扩展性,建议仍是优先使用接口。如今不须要,谁能知道后续需不须要呢?因此,让咱们大胆的使用接口吧~