🔌 TS 的 Interface 了解一下?

14,990 阅读5分钟

20190318235331.png

Interface 是一种描述对象或函数的东西。你可以把它理解为形状,一个对象需要有什么样的属性,函数需要什么参数或返回什么样的值,数组应该是什么样子的,一个类和继承类需要符合什么样的描述等等。下文分七个部分,一起看一下对象接口、函数接口、可索引类型接口、类接口是如何定义的,以及接口的继承、定义混合类型的接口和继承类的接口是如何使用的。📕

对象 Interface

  1. 设置需要存在的普通属性
  2. 设置可选属性
  3. 设置只读属性
  4. 另外还可以通过 as 或 [propName: string]: any 来制定可以接受的其他额外属性
interface Person {
    name: string
    bool?: boolean
    readonly timestamp: number
    readonly arr: ReadonlyArray<number> // 此外还有 ReadonlyMap/ReadonlySet
}

let p1: Person = {
    name: 'oliver',
    bool: true, // ✔️️ 可以设置可选属性 并非必要的 可写可不写
    timestamp: + new Date(), // ✔️ 设置只读属性
    arr: [1, 2, 3] // ✔️ 设置只读数组
}

let p: Person = {
    age: 'oliver', // ❌ 多出来的属性
    name: 123 // ❌ 类型错误
}

p1.timestamp = 123 // ❌ 只读属性不可修改
p1.arr.pop() // ❌ 只读属性不可修改

函数 Interface

Interface 还可以用来规范函数的形状。Interface 里面需要列出参数列表返回值类型的函数定义。写法如下:

  1. 定义了一个函数接口
  2. 接口接收三个参数并且不返回任何值
  3. 使用函数表达式来定义这种形状的函数
interface Func {
    // ✔️ 定于这个函数接收两个必选参数都是 number 类型,以及一个可选的字符串参数 desc,这个函数不返回任何值
    (x: number, y: number, desc?: string): void
}

const sum: Func = function (x, y, desc = '') {
    // const sum: Func = function (x: number, y: number, desc: string): void {
    // ts类型系统默认推论可以不必书写上述类型定义
    console.log(desc, x + y)
}

sum(32, 22)

可索引类型 Interface

这种 Interface 描述了索引类型的形状,规定索引返回的值的类型

interface StringSet {
    readonly [index: number]: string // ❗ 需要注意的是 index 只能为 number 类型或 string 类型
    length: number // ✔️ 还可以指定属性
}

let arr1: StringSet = ['hello', 'world']
arr1[1] = '' // ✔️ 可以设置为只读防止给索引赋值
let arr: StringSet = [23,12,3,21] // ❌ 数组应为 string 类型

类 Interface

Interface 也可以用来定义一个类的形状。需要注意的是类 Interface 只会检查实例的属性,静态属性是需要额外定义一个 Interface;比如:

// 🥇 PersonConstructor 是用来检查静态部分的
interface PersonConstructor {
    new (name: string, age: number) // ✔️ 这个是用来检查 constructor 的
    typename: string // ✔️ 这个是用来检查静态属性 typename 的
    logname(): void // ✔️ 这个用来检查静态方法 logname 的
}
// 🥈 PersonInterface 则是用来检查实例部分的
interface PersonInterface {
    // new (name: string, age: number) // ❌ 静态方法的检查也不能写在这里 这样写是错误的
    log(): void // : 这里定义了实例方法 log
}

// class Person implements PersonInterface, PersonInterface { ❌ 这样写是错误的
const Person: PersonConstructor = class Person implements PersonInterface {
    name: string
    age: number
    static typename = 'Person type' // 这里定义了一个名为 typename 的静态属性
    static logname() { // 这里定义了一个名为 logname 的静态方法
        console.log(this.typename)
    }
    constructor(name: string, age: number) { // constructor 也是静态方法
        this.name = name
        this.age = age
    }
    log() { // log 是实例方法
        console.log(this.name, this.age)
    }
}

⚠️ 一定要记住静态属性和方法的检查、实例属性和方法的检查是不同的 Interface

Interface 的继承

跟 class 一样,使用 extens 继承,更新新的形状,比方说继承接口并生成新的接口,这个新的接口可以设定一个新的方法检查。

看个例子🌰:

interface PersonInfoInterface { // 1️⃣ 这里是第一个接口
    name: string
    age: number
    log?(): void
}

interface Student extends PersonInfoInterface { // 2️⃣ 这里继承了一个接口
    doHomework(): boolean // ✔️ 新增一个方法检查
}
interface Teacher extends PersonInfoInterface { // 3️⃣ 这里又继承了一个接口
    dispatchHomework(): void // ✔️ 新增了一个方法检查
}

// interface Emmm extends Student, Teacher // 也可以继承多个接口

let Alice: Teacher = {
    name: 'Alice',
    age: 34,
    dispatchHomework() { // ✔️ 必须满足继承的接口规范
        console.log('dispatched')
    }
}

let oliver: Student = {
    name: 'oliver',
    age: 12,
    log() {
        console.log(this.name, this.age)
    },
    doHomework() { // ✔️ 必须满足继承的接口规范
        return true
    }
}

混合类型的 Interface

混合类型的接口就是使用同一个 Interface 来描述函数或者对象的属性或方法,比如一个函数接收什么参数,输出什么结果,同时这个函数有另外什么方法或属性之类的。🌰

interface Counter {
    (start: number): void // 1️⃣ 如果只有这一个那么这个接口是函数接口
    add(): void // 2️⃣ 这里还有一个方法,那么这个接口就是混合接口
    log(): number // 3️⃣ 这里还有另一个方法
}

function getCounter(): Counter { // ⚠️ 它返回的函数必须符合接口的三点
    let count = 0
    function counter (start: number) { count = start } // counter 方法函数
    counter.add = function() { count++ } // add 方法增加 count
    counter.log = function() { return count } // log 方法打印 count
    return counter
}

const c = getCounter()
c(10) // count 默认为 10
c.add()
console.log(c.log())

继承类的 Interface

Interface 不仅能够继承 Interface 还能够继承类,再创建子类的过程中满足接口的描述就会必然满足接口继承的类的描述

class Person {
    type: string // ❗️这里是类的描述
}

interface Child extends Person { // ❗️Child 接口继承自 Person 类,因此规范了 type 属性
    log(): void
    // 这里其实有一个 type: string
}

// ⚠️ 上面的 Child 接口继承了 Person 对 type 的描述,还定义了 Child 接口本身 log 的描述

// 🥇 第一种写法
class Girl implements Child {
    type: 'child' // 接口继承自 Person 的
    log() {} // 接口本身规范的
}

// 🥈 第二种写法
class Boy extends Person implements Child { // 首先 extends 了 Person 类,然后还需满足 Child 接口的描述
    type: 'child'
    log() {}
}

这个接口的定义和使用如下图所示:

20190318234436.png

参考