接口和类型别名十分类似,在大多状况下二者能够调换。在写TS的时候,想必大家都问过本人这个问题,我到底应该用哪个呢?心愿看完本文会给你一个答案。晓得什么时候应该用哪个,首先应该理解二者之间的相同点和不同点,再做出抉择。
接口 vs 类型别名 相同点
1. 都能够用来形容对象或函数
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;
2. 都能够扩大
两者的扩大形式不同,但并不互斥。接口能够扩大类型别名,同理,类型别名也能够扩大接口。
接口的扩大就是继承,通过 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}
接口 vs 类型别名不同点
1. 类型别名更通用(接口只能申明对象,不能重命名根本类型)
类型别名的左边能够是任何类型,包含根本类型、元祖、类型表达式(&
或|
等类型运算符);而在接口申明中,左边必须为构造。例如,上面的类型别名就不能转换成接口:
type A = numbertype B = A | string
2. 扩大时体现不同
扩大接口时,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将尽其所能把扩大和被扩大的类型组合在一起,而不会抛出编译时谬误。
type A = { good(x: number): string, bad(x: number): string}type B = A & { good(x: string | number) : string, bad(x: number): number }
3. 屡次定义时体现不同
接口能够定义屡次,屡次的申明会合并。然而类型别名如果定义屡次,会报错。
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'.}
到底应该用哪个
如果接口和类型别名都能满足的状况下,到底应该用哪个是咱们关怀的问题。感觉哪个都能够,然而强烈建议大家只有能用接口实现的就优先应用接口,接口满足不了的再用类型别名。
为什么会这么倡议呢?其实在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 & PointYtype Point = PointXY & PointZconst 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
。
这个例子能够同时解释译文中第二个和最初一个不同点。
论断
有的同学可能会问,如果我不须要组合只是单纯的定义类型的时候,是不是就能够轻易用了。然而为了代码的可扩展性,倡议还是优先应用接口。当初不须要,谁能晓得后续需不需要呢?所以,让咱们大胆的应用接口吧~