一、什么是EventBus
- EventBus是一个开源库,利用发布/订阅者模式对项目进行解耦
- 利用很少的代码来实现多组件间通信
- 在Vue中可以使用它来作为沟通桥梁的概念,就像所有组件共用的相同的事件中心
- 如图所示,可以向该中心注册发送事件或者接收事件,这也就说明了所有组件可以上下平行地通知其他组件
二、 在vue2 中如何使用eventBus
🌼 创建事件总线
- 第一种方法是可以直接在
main.js
初始化,在vue的原型对象上定义$eventBus
//main.js
Vue.prototype.$eventBus = new Vue()
- 第二种可以新建一个.js文件,在其中引入Vue并导出它的一个实例
//event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
实质上它算一个不具备DOM的组件,我们需要的仅仅是它的实例方法
🌼 引入event-bus.js
- 仅使用上述第二种方法才需要这一步
- 每一个使用到eventBus的组件都需要引入event-bus.js
import EventBus form './eventBus.js'
🌼触发事件
- 发布者角色
- 使用方法
//对于方式一
this.$eventBus.$emit('eventName',params....)
//对于方式二
EventBus.$emit('eventName',params....)
- 使用实例 --在页面A发布事件
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
import EventBus from './eventBus.js'
export default {
methods: {
//发送事件
sendMsg() {
EventBus.$emit("aPostMsg", '页面A发出的消息');
//或者
this.$eventBus.$emit('aPostMsg','页面A发出的消息');
}
}
};
🌼 监听事件
- 订阅者角色
- 使用方法
//对于方式一
this.$eventBus.$on('evnetName',(params...) => {
//do something....
})
//对于方式二
EventBus.$on('eventName', (params...) => {
//do something....
})
- 使用实例 --在页面B订阅事件
<script>
import EventBus from './eventBus.js'
export default {
data(){
return {}
},
mounted() {
EventBus.$on("aPostMsg", (msg) => {
console.log(msg) //页面A发出的消息
});
//或者
this.$eventBus.$on("aPostMsg", (msg) => {
console.log(msg) //页面A发出的消息
});
}
};
如果只是这样的,监听事件会一直存在,反复进入到接收组件内操作获取数据我们只期待发送一次,但是会发生多次
🌼 移除事件监听
-
原因:
- 避免在监听的时候,如上述所说的,事件被反复监听
- 因为是热更新,事件可能会被多次绑定监听
- 没有及时移除eventBus也可能会导致内容泄漏
-
那么如何移除事件监听呢
- 移除单个事件
//对于方式一 EventBus.$off('aPostMsg') //对于方式二 this.$eventBus.$off('aPostMsg')
- 移除所有事件
//对于方式一 EventBus.$off() //对于方式二 this.$eventBus.$off()
- 移除单个事件的某个回调
//对于方式一 EventBus.$off('aPostMsg',callback) //对于方式二 this.$eventBus.$off('aPostMsg',callback)
-
使用实例
- 在beforeDestroyed(){}中监听移除事件
beforeDestroyed(){ EventBus.$off('aPostMsg') //或者 this.$eventBus.$off('aPostMsg') }
三、vue3 中的 eventBus
🌷 Vue3的变化
- 在 Vue3.0+ 的版本中,Vue 移除了
$on
、$off
和$once
方法;并且没有提供兼容的功能 - 在vue3中推荐我们使用
mitt
事件总线传递数据,其实mitt的使用方式和vue原本的自定义事件使用方式相同 -详细可查看文档
🌷 mitt的使用
- 在根目录终端执行
```js
npm install --save mitt
```
- 将方法导出使用
```js
// eventBus.js
import emitter from 'tiny-emitter/instance'
export default {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
}
```
四、 手写 eventBus
🌻 实现思路:
- 声明了一个
EventBus
类,用来处理发布订阅,用events
对象存储监听的事件, 用map存储id和监听事件的回调函数- 这里为什么用map可以参考一下彻底搞懂WeakMap和Map - 掘金 (juejin.cn)
- 订阅 了事件thing1,thing2.... 先判断该事件是否存储过,如果没有存储过就初始化map,存储过就往map里面存储键值对
- 触发了事件,则从
events
取出该事件的回调列表,利用for..of..对其进行遍历调用 - 移除事件,分为三种情况,不传参的话则默认全部清空,传一个参数则移除该事件的全部回调函数,传两个则移除该事件上为该id的回调函数
🌻 实现代码
class EventBus {
constructor() {
this.events = new Map(); // 用于存储所有订阅事件
this.callbackId = 0; //用于记录事件id,通过返回id的形式让用户可以单独取消该回调
}
// 订阅事件
$on(name, callback) {
//获取该事件的回调函数
let eventList = this.events.get(name);
//该事件如果没有被存储过的话则先初始化为map对象
if(!eventList) {
eventList = new Map()
}
//每次存储回调函数的时候都让id++
const id = this.callbackId++;
//将回调函数放入列表
eventList.set(id, callback);
//将列表放入events
this.events.set(name, eventList);
//返回的id主要是用来移除该回调函数
return id;
}
// 发布事件
$emit(name, ...args) {
// 获取存储的事件回调函数对象
const eventList = this.events.get(name);
// 执行所有回调函数
if(eventList){
for (const [id, callback] of eventList) {
callback.apply(this,[...args])
}
}
}
// 移除事件
$off(name, id) {
//如果不传参,则全部清空
if(name == undefined) {
return this.events.clear();
}
if(id == undefined) {
return this.events.delete(name);
}
return this.events.get(name).delete(id);
}
}
🌻测试一下
- 主要有事件
name1
,name2
,分别订阅了两个回调函数
const evb = new EventBus();
const name1Id = evb.$on('name1' , (params1)=> {
console.log(`我是name1的第一个订阅事件,参数为${params1}`)
})
evb.$on('name1', ()=>{
console.log(`我是name1的第二个订阅事件`)
})
evb.$on('name2', (params2)=> {
console.log(`我是name2的第一个订阅事件,参数为${params2}`)
})
evb.$on('name2', (params2)=> {
console.log(`我是name2的第二个订阅事件`)
})
- 然后分别触发这两个事件,可以看到两个事件的两个回调函数都被触发执行
console.log('---触发事件name1-----')
evb.$emit('name1', '哈哈哈')
console.log('---触发事件name2-----')
evb.$emit('name2', '今天是9月30号')
- 测试移除事件
- 首先是先移除了name2事件,那么再次触发name2事件的时候已经没有触发回调函数了
- 再者是通过id移除了name1事件的第一个回调函数,发现再次触发事件,只执行第二个
console.log('---这里移除了name2事件---')
evb.$off('name2');//移除name2事件
console.log('---再次触发事件name2----')
evb.$emit('name2');
console.log('---移除事件1上的第一个订阅事件----');
evb.$off('name1',name1Id);
console.log('---再次触发事件name1-----')
evb.$emit('name1')