TypeScript Interface vs Type知多少

更新日期: 2021-04-18阅读: 1.3k标签: 接口

接口和类型别名非常相似,在大多情况下二者可以互换。在写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。

这个例子可以同时解释译文中第二个和最后一个不同点。


结论

有的同学可能会问,如果我不需要组合只是单纯的定义类型的时候,是不是就可以随便用了。但是为了代码的可扩展性,建议还是优先使用接口。现在不需要,谁能知道后续需不需要呢?所以,让我们大胆的使用接口吧~

来源:https://segmentfault.com/a/1190000039834284


链接: https://fly63.com/article/detial/10220

为什么前后端分离了,你比从前更痛苦?

前后端分离可以让我们的职责更清晰,打破前端发挥的局限,工作解耦之后能更好的提高开发效率。然而因为没有规划好开发流程,导致了我们没有发挥出其应有的价值,造成了更多的浪费。

前后端分离项目的跨域及保持Session会话

当Web项目前后端分离开发的时候, 由于域名不一致, 会出现无法请求和无法维持会话的情况,在前端Ajax请求后台的时候, 打开控制台可以看到, 每一次请求之前都会有一次OPTIONS类型的请求

PHP面向对象(抽象类与抽象方法、接口的实现)

任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。定义为抽象的类不能被实例化。 被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。

vue 项目接口管理

在vue开发中,会涉及到很多接口的处理,当项目足够大时,就需要定义规范统一的接口,如何定义呢?方法可能不只一种,本文使用axios+async/await进行接口的统一管理。

免费的公共API接口_WebService接口大全

这篇文章为大家整理一下免费,常用的的WebService接口,列举一些搜集到的免费的公共API接口,希望对你有所帮助,天气预报Web服务,数据来源于中国气象局;IP地址来源搜索 WEB 服务;随机英文、数字和中文简体字

常用HTTP接口测试工具对比

从功能上Jmeter最为强大,可以测试各种类型的接口,不支持的也可以通过网上或自己编写的插件进行扩展。SoapUI专门针对HTTP类型的两种接口,其初衷更是专门测试Soap类型接口,对于其他协议的接口不支持

TypeScript接口(Interfaces)来定义对象的类型

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)

你不得不了解的前后端分离原理!

前后端分离已成为互联网项目开发的业界标准使用方式,通过nginx+tomcat的方式(也可以中间加一个nodejs)有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构

vue项目接入mock&& axios 通用配置

兵马未动,粮草先行; 同理,项目开发过程中经常会出现接口未出, 前端页面已搭建完毕的情况;此时为了提高前端的开发效率,解放生产力,我们 FE 可以按照预定的接口文档做一些接口模拟的工作

vue中使用proxy配置不同端口和ip接口

使用vue-cli创建的项目,开发地址是localhost:8080,由于后台开发不同的模块,导致每个模块请求的ip和端口号不一致,解决问题:在vue.config.js中配置不同的端口号

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!