听过EventBus吗?

2,332 阅读3分钟

一、什么是EventBus

  • EventBus是一个开源库,利用发布/订阅者模式对项目进行解耦
  • 利用很少的代码来实现多组件间通信
  • 在Vue中可以使用它来作为沟通桥梁的概念,就像所有组件共用的相同的事件中心
  • 如图所示,可以向该中心注册发送事件或者接收事件,这也就说明了所有组件可以上下平行地通知其他组件 image.png

二、 在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和监听事件的回调函数
  • 订阅 了事件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号')

image.png

  • 测试移除事件
    • 首先是先移除了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')

image.png