目标
需要实现一个 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)
}
}
测试
在编写代码的过程中,编辑器能给我们代码提示,输入参数的类型错误时编辑器也会给我们警告
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)
}
}