接口和类型别名十分类似,在大多状况下二者能够调换。在写 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 = number
type 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 & 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
。
这个例子能够同时解释译文中第二个和最初一个不同点。
论断
有的同学可能会问,如果我不须要组合只是单纯的定义类型的时候,是不是就能够轻易用了。然而为了代码的可扩展性,倡议还是优先应用接口。当初不须要,谁能晓得后续需不需要呢?所以,让咱们大胆的应用接口吧~