TS 中的接口

刚接触 typescript 的同学可能会对接口这个概念感到陌生,毕竟 js 里可没有这么个玩意儿。本篇文章就来介绍一下,到底什么是接口?它又能起到什么作用?

基础用法

接口是对象的状态和行为的抽象,使用接口来对一个对象的属性和方法的类型进行声明。比如,我们需要定义个歌手对象,其有以下 4 种属性:

  1. id:字符串类型,只读的,必需的;
  2. name:字符串类型,必需的;
  3. age:数字类型,必需的;
  4. gender:字符串类型,可选的。

那么接口就可以这么写: 使用关键字 interface 定义接口,写法同定义 class 类相似,接口名后不用跟括号,我习惯接口名以 I 开头,{} 里的内容不需要用逗号分割:

typescript 复制代码
interface ISinger {
  readonly id: string // readonly 写在属性名前,代表只读属性,赋值后不能修改
  name: string
  age: number
  gender?: string // 属性名后跟个问号代表是可选属性
  sing(): void // 定义方法
  run: () => void // 也可以写成这样定义方法
}

在定义一个对象的时候,类型就可以是接口 ISinger

typescript 复制代码
const Jay: ISinger = {
  id: 'z1234',
  name: 'Jay',
  age: 22,
  sing() { },
  run() { }
}

除了可选属性,其它属性的必须要进行定义,不能多,也不能少。但是,如果是下面这种写法,则又允许将拥有 IPerson 接口中不存在的属性的对象 temObj 赋值给 Jay

typescript 复制代码
interface IPerson {
  name: string
  age: number
}

const temObj = {
  name: 'Jay',
  age: 22,
  height: 12 // IPerson 中不存在的属性
}

const Jay: IPerson = temObj

这种现象的原因,与下面介绍的 freshness 检测规则有关:

Freshness

在对字面量进行赋值时,ts 的类型检测有个叫做 freshness(字面意思为"新鲜") 的规则。比如下例中 singer 类型为 singerType,而直接赋值的字面量对象里多了个 sing 方法,就会报错:

但如果我们把字面量对象先赋值给 temp ,在把 temp 赋值给 singer,就不会报错:

typescript 复制代码
type singerType = {
  name: string
  age: number
}
const temp = {
  name: 'Jay',
  age: 18,
  sing() { }
}

const singer: singerType = temp

这是因为 ts 在做类型检测时,会默认把 temp 中多出来的 sing 属性去除,所以,如果我们之后调用 sing 方法,类型检测也会报错:

类对接口的实现

implements

类除了可以继承类,也可以实现(implements)接口:

typescript 复制代码
interface ISinger {
  sing(): void // 该方法没有任何的实现
}

class Singer implements ISinger {
  sing() {
    console.log('作为一名歌手一年出张专辑不过分吧~')
  }
}

const Jay = new Singer()
Jay.sing()

接口 ISinger 定义了 sing 方法,类 Singer 实现了 ISinger,则 Singer 类中也必须定义 sing 方法。

一个类可以实现多个接口

一个类只可以继承自一个类,但可以实现多个接口,接口之间用 , 分割。每个接口中的内容都要真正实现:

typescript 复制代码
interface ISinger {
  sing(): void
}
interface IFatPersong {
  eat(): void
}

class Singer implements ISinger, IFatPersong {
  sing() {
    console.log('作为一名歌手一年出张专辑不过分吧~')
  }
  eat() {
    console.log('就知道喝奶茶')
  }
}

接口继承接口

和类一样,接口也可以相互继承,一个接口可以继承多个接口。上面一个类可以实现多个接口例子也可以用下面这种方式:

typescript 复制代码
interface IFatSinger extends ISinger, IFatPersong {}

// 直接实现上面这个类
class Singer implements IFatSinger {
  sing() {}
  eat() {}
}

注意:接口之间是继承(extends)关系,类和接口之间是实现(implements)关系。

和 type 对比

《TS 中的类型》中介绍了定义类型别名的 type, 它和 interface 都可以定义对象的类型,它们有什么区别呢?

  • 写法的区别,type 有用到 =,是赋值式的写法;而 interface 的定义没有用到 =,是声明式的写法:
typescript 复制代码
type PersonType = {
  name: string
  age?: number
}

interface IPerson {
  name: string
  age?: number
}
  • type 可以定义的类型范围比 interface 大,一般定义函数或者联合类型时,使用 type,而 interface 用于定义对象,并且官方文档有下面这么一句话 :

在大多数情况下,你可以根据个人喜好进行选择,TypeScript 会告诉你它是否需要其他类型的声明。如果您想要启发式方法,可以使用 interface 直到你需要使用 type 中的功能。

  • interface 可以重复定义,而 type 不可以。多次定义的 ISinger 接口的属性会合并,所以 Singer 需要同时有 singeat 方法:
typescript 复制代码
interface ISinger {
  sing(): void
}
interface ISinger {
  eat(): void
}

class Singer implements ISinger {
  sing() { }
  eat() { }
}
  • 接口可以被类实现,并且支持继承,而 type 则不行。

索引签名(Index Signatures)

当我们想定义一个属性名均为数字类型的对象 obj1,可以设置接口的 key 的类型,当然这里第 2 行的 key 只是个形参,是自定义的,类型可以是stringnumbersymbol

typescript 复制代码
interface IObj1 {
  [key: number]: string
}
const obj1: IObj1 = {
  0: 'Jay',
  1: 'Join',
  2: 'Eson'
}

interface IObj2 {
  [key: symbol]: string
}
const sym: symbol = Symbol()
const obj2: IObj2 = {
  [sym]: 'Jay',
}

obj1 的类型被注释为 IObj1 后,obj1 的属性名就只能是数字了。

另外还有一个细节,索引类型为 number 的值的类型,必须是索引类型为 string 的值的子类型:

typescript 复制代码
interface IObj {
  [key1: string]: string | number
  [key2: number]: string
}

如果像下面这样,就会报错:

因为在 js 中,使用数字进行索引时,实际上会在索引到对象之前将数字转换成字符串,即 arr[1]arr['1'] 是一样的。

同理,如果定义了索引类型为 string 的索引签名,又定义了其它属性,那么其它属性的值的类型,也必须是索引类型为 string 的值的类型的子类型:

typescript 复制代码
interface IObj {
  [key: string]: string | number
  name: string
}

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试