前言 使用 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 55 56 57 58 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 74 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 = E;module .exports .TinyEmitter = E;
后记 虽然事件总线简单易用,但是当代码复杂度上升到一定程度之后,过多的事件监听会让数据流变得晦涩难懂
官方并不鼓励使用全局的事件总线来进行组件间的通信
我们可以通过其他的方法来实现相同的效果
比如提到的
prop
和 emit
(父子组件通信)
provide
和 inject
(父传后代)
expose
和 ref
(子传父)
全局状态管理 Vuex
或者 Pinia
(提取全局状态)
v-slot
暴露变量(子传父)