前言 使用 mitt 来作为 Vue3 中的事件总线(EventBus)
正文 在 Vue2 中,我们习惯使用 new Vue() 来创建一个 Vue 实例
只使用这个实例来调用 $on 或者 $off 来添加或者删除事件回调,使用 $emit 来调用改事件的所有回调
这样就可以在不同组件之间进行数据传递
1 2 3 4 import Vue from 'vue' export const emitter = new Vue ()
1 2 3 4 import { emitter } from './emitter.js' Vue .prototype .$emitter = emitter
使用 emitter
1 2 3 4 5 6 7 8 export default { methods : { send ( ) { this .$emitter .$emit('eventName' , 'data' ) }, }, }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export default { methods : { callback (data ) { console .log (data) }, }, mounted ( ) { this .$emitter .$on('eventName' , this .callback ) }, beforeDestroy ( ) { this .$emitter .$off('eventName' , this .callback ) }, }
在 Vue3 中,官方已经不推荐使用 new Vue() 来构造事件总线了
而推荐使用 mitt 或者 tiny-emitter 库来进行替代
事件总线 - 事件 API | Vue.js
这两个类库的实现都是差不多的
mitt
mitt - Github
使用 TS 编写,有完整的类型推断
支持 * 作为事件名
* 作为事件名即任何 emit 都会触发这些事件回调
源码如下(删除部分 TS 类型代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 export default function mitt<Events extends Record <EventType , unknown >>( all?: EventHandlerMap <Events >, ): Emitter <Events > { all = all || new Map () return { all, on<Key extends keyof Events >(type : Key , handler : GenericEventHandler ) { const handlers : Array <GenericEventHandler > | undefined = all!.get (type ) if (handlers) { handlers.push (handler) } else { all!.set (type , [handler] as EventHandlerList <Events [keyof Events ]>) } }, off<Key extends keyof Events >(type : Key , handler?: GenericEventHandler ) { const handlers : Array <GenericEventHandler > | undefined = all!.get (type ) if (handlers) { if (handler) { handlers.splice (handlers.indexOf (handler) >>> 0 , 1 ) } else { all!.set (type , []) } } }, emit<Key extends keyof Events >(type : Key , evt?: Events [Key ]) { let handlers = all!.get (type ) if (handlers) { ;(handlers as EventHandlerList <Events [keyof Events ]>).slice ().map ((handler ) => { handler (evt!) }) } handlers = all!.get ('*' ) if (handlers) { ;(handlers as WildCardEventHandlerList <Events >).slice ().map ((handler ) => { handler (type , evt!) }) } }, } }
tiny-emitter
tiny-emitter - Github
有 index.d.ts 文件,不过源码使用 js 编写
不支持 * 作为事件名,不过封装了 once 方法
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 function E ( ) {}E.prototype = { on : function (name, callback, ctx ) { var e = this .e || (this .e = {}) ;(e[name] || (e[name] = [])).push ({ fn : callback, ctx : ctx, }) return this }, once : function (name, callback, ctx ) { var self = this function listener ( ) { self.off (name, listener) callback.apply (ctx, arguments ) } listener._ = callback return this .on (name, listener, ctx) }, emit : function (name ) { var data = [].slice .call (arguments , 1 ) var evtArr = ((this .e || (this .e = {}))[name] || []).slice () var i = 0 var len = evtArr.length for (i; i < len; i++) { evtArr[i].fn .apply (evtArr[i].ctx , data) } return this }, off : function (name, callback ) { var e = this .e || (this .e = {}) var evts = e[name] var liveEvents = [] if (evts && callback) { for (var i = 0 , len = evts.length ; i < len; i++) { if (evts[i].fn !== callback && evts[i].fn ._ !== callback) liveEvents.push (evts[i]) } } liveEvents.length ? (e[name] = liveEvents) : delete e[name] return this }, } module .exports = Emodule .exports .TinyEmitter = E
后记 虽然事件总线简单易用,但是当代码复杂度上升到一定程度之后,过多的事件监听会让数据流变得晦涩难懂
官方并不鼓励使用全局的事件总线来进行组件间的通信
我们可以通过其他的方法来实现相同的效果
比如提到的
prop 和 emit (父子组件通信)
provide 和 inject (父传后代)
expose 和 ref (子传父)
全局状态管理 Vuex 或者 Pinia (提取全局状态)
v-slot 暴露变量(子传父)