用 TypeScript 实现一个 EventEmitter

878 阅读2分钟

目标

需要实现一个 EventEmitter 类,该类中有 on / emit / off / once 四个方法,能约束输入的参数,并且有代码提示。

具体实现

事件存储中心

首先定义回调函数的类型

type Callback<T = any> = (args: T) => void

用 map 来存放注册的事件与回调,一个事件可能注册多个回调函数,所以使用 set 来存储所有回调函数

class EventEmitter<Events extends Record<keyof any, any>> {
  private map: Map<keyof Events, Set<Callback>> = new Map()
}

on

将事件回调函数存储到对应的事件上,第一次注册事件需要初始化 set

class EventEmitter<Events extends Record<keyof any, any>> {
  on<Name extends keyof Events>(name: Name, cb: Callback<Events[Name]>) {
    let cbs = this.map.get(name)
    !cbs && this.map.set(name, cbs = new Set())
    cbs.add(cb)
  }
}

emit

获取到事件对应的回调函数依次执行

class EventEmitter<Events extends Record<keyof any, any>> {
  emit<Name extends keyof Events>(name: Name, args: Events[Name]) {
    let cbs = this.map.get(name)
    if (!cbs) return
    cbs.forEach(cb => cb(args))
  }
}

off

不传入参数删除所有事件,传入 name 删除这个事件的所有回调,传入 name 和 cb 删除指定事件

class EventEmitter<Events extends Record<keyof any, any>> {  
  off<Name extends keyof Events>(name?: Name, cb?: Callback<Events[Name]>) {
    if (!name) {
      this.map.clear()
      return
    }
    if (!cb) {
      this.map.delete(name)
      return
    }
    const cbs = this.map.get(name)
    cbs && cbs.delete(cb)
  }
}

once

先用 on 注册,事件执行后用 off 取消订阅

class EventEmitter<Events extends Record<keyof any, any>> {
  once<Name extends keyof Events>(name: Name, cb: Callback<Events[Name]>) {
    const one: Callback<Events[Name]> = (args) => {
      cb(args)
      this.off(name, one)
    }
    this.on(name, one)
  }
}

测试

在编写代码的过程中,编辑器能给我们代码提示,输入参数的类型错误时编辑器也会给我们警告

image-20220725214604568

image-20220725214753895

image-20220725214837492

const emitter = new EventEmitter<{
  911: [string, number]
  helloworld: string
}>()

emitter.on(911, ([str, num]) => console.log(`911事件被触发: ${str} ${num}`))
// 911事件被触发: foo 110
emitter.emit(911, ['foo', 110])

const fn = ([str, num]: [string, number]) => {
  console.log(`911事件又被触发了: ${str} ${num}`)
}
emitter.on(911, fn)
// 911事件被触发: bar 120
// 911事件又被触发了: bar 120
emitter.emit(911, ['bar', 120])
emitter.off(911, fn)
// 911事件被触发: foo bar 119
emitter.emit(911, ['foo bar', 119])
emitter.off(911)

emitter.once('helloworld', (str) => console.log(`hello ${str}`))
// hello world
emitter.emit('helloworld', 'world')
emitter.emit('helloworld', 'world')

完整的代码

type Callback<T = any> = (args: T) => void

class EventEmitter<Events extends Record<keyof any, any>> {
  private map: Map<keyof Events, Set<Callback>> = new Map()

  on<Name extends keyof Events>(name: Name, cb: Callback<Events[Name]>) {
    let cbs = this.map.get(name)
    !cbs && this.map.set(name, cbs = new Set())
    cbs.add(cb)
  }

  emit<Name extends keyof Events>(name: Name, args: Events[Name]) {
    let cbs = this.map.get(name)
    if (!cbs) return
    cbs.forEach(cb => cb(args))
  }

  off<Name extends keyof Events>(name?: Name, cb?: Callback<Events[Name]>) {
    if (!name) {
      this.map.clear()
      return
    }
    if (!cb) {
      this.map.delete(name)
      return
    }
    const cbs = this.map.get(name)
    cbs && cbs.delete(cb)
  }

  once<Name extends keyof Events>(name: Name, cb: Callback<Events[Name]>) {
    const one: Callback<Events[Name]> = (args) => {
      cb(args)
      this.off(name, one)
    }
    this.on(name, one)
  }
}