目录
背景
学习Vue的小伙伴一定不陌生:“Vue中组件通信方式有哪些?”。可能我们回答props/$emit、provide/inject、$listener/$attrs、$refs。这些方式更适合做垂直的深度通信,但在一些父子关系复杂的情况下,eventBus是一个必要的答案,因为在这种场景下,eventBus能够在不引用任何第三方依赖的情况下帮助我们实现复杂组件关系之间的通信。接下来,本文会根据作者的理解,对事件总线相关的知识展开分享。
概念
事件总线(eventBus),是一种跨组件通信的方式,是跨组件通信的媒介。可以类比成一个“事件中心”,基于发布订阅模式,组件可以将消息广播,通知所有订阅者,从而达到组件之间的通信效果。
如果是父子组件的关系那就老老实实用props/$emit吧,父子之间的通信毕竟是局部可控的,关系单一并不复杂。当然,其他复杂的情况也可以使用父子通信的方式,但随着业务复杂度越来越高,组件之间的关系也就越来越复杂,用父子通信的方式,可能会在关系链途径的组件中写下许多与组件业务无关的代码,大大增加了维护难度。
事件总线就非常适用于兄弟组件或组件之间无直接关系的情况(如下图),这种使用方式对其他组件的本身没有任何影响,是以“事件中心”为媒介,来做跨组件的通信。
事件总线既然这么简单方便,能做各种关系的组件间通信,那么是否可以都用事件总线代替其他通信方式呢?答案是可以但非常不建议!原因如下:
- 维护困难:事件总线引入了全局自定义事件,如果滥用则会增加维护成本,还容易造成变量污染。
- 违背组件的设计理念:我们为了保持良好的可维护性,希望组件之间呈单向数据流,事件总线数据流不明确,不易管理和控制。
- 不可预估的性能问题:如果一个页面中有很多监听器,频繁去触发响应,那么对性能方面是有非常大的压力。
- 安全漏洞:放在全局相当于暴露了出来,可能被恶意利用。
- 编码风险:在使用时有忘记卸载监听事件的风险,有一定的使用门槛。
实现方式
实现方式较简单,只需三个事件,注册监听事件、卸载监听事件以及消息发送事件。这里使用Set数据结构,保证得到的事件stack集合是不重复的。
- 注册监听事件:调用$on注册激活监听事件,是广播对应内容的接收者。
- 卸载监听事件:调用$off移除不使用的监听,减少不必要的监听器在内存中残留。
- 消息发送事件:调用$emit广播内容,是数据通信的发送者。
const stack; // 用来保存全局事件
export default = {
// 注册监听事件
$on(eName, handle) {
if(!stack[eName]) {
stack[eName] = new Set();
}
stack[eName].add(handle);
},
// 卸载监听事件
$off(eName, handle) {
if(!stack[eName]) {
return false;
}
stack[eName].delete(handle);
},
// 消息发送事件
$emit(eName, ...args) {
if(!stack[eName]) {
return false;
}
for(const handle of stack[eName]) {
handle(...args);
}
},
};
调用的方式也十分简单,首先需要规定一个事件名称,抽出来一个具名方法,根据如下代码按照实际情况嵌入业务中。
注意:$off通常需要放在beforeDestroy生命周期钩子中使用,一定不要忘记使用!
import eventBus from 'eventBus';
const eventName = '事件名称';
const onFn = (args) => {
// 处理接收的数据
};
eventBus.$on(eventName, onFn);
eventBus.$emit(eventName, '参数args');
eventBus.$off(eventName, onFn);
实际上,Vue已经帮我们把$on、$off、$emit这三个方法实现了,如果我们要引入事件总线其实一两行代码就够了。我们导出的是vm实例,我们打印vm实例是可以在vm的原型对象上看到有这么几个方法的。
import Vue from 'vue';
export default new Vue({});
再简便一点,不需要每次使用都引入的话,那就是在main.js入口文件中将eventBus注册到vue实例中。
import Vue from 'vue';
import eventBus from './eventBus';
Vue.prototype.$bus = eventBus; // 挂载到vue实例上 调用时this.$bus即可
如何管理
事件总线不能滥用,那么在项目中如何改善呢?
- 开发设计评审:在着手开发前,使用事件总线,需要阐明必要性,非必要尽量不用。
- 代码评审:在代码合并时,负责人需要着重评审事件总线的应用场景,以及$off是否合理使用。
- 落实规范:代码规范中应落实列举事件总线使用场景,维护更新事件总线使用文档。
- 适当替换:事件总线过多时,可以考虑使用其他方法如vuex、pinia等方式代替。