关于typescript:TypeScript-Interface-vs-Type知多少

38次阅读

共计 3438 个字符,预计需要花费 9 分钟才能阅读完成。

接口和类型别名十分类似,在大多状况下二者能够调换。在写 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

这个例子能够同时解释译文中第二个和最初一个不同点。

论断

有的同学可能会问,如果我不须要组合只是单纯的定义类型的时候,是不是就能够轻易用了。然而为了代码的可扩展性,倡议还是优先应用接口。当初不须要,谁能晓得后续需不需要呢?所以,让咱们大胆的应用接口吧~

正文完
 0