Vue组件通信中事件总线(eventBus)的使用

240 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

之前公司的项目中使用 eventBus 进行组件通信时遇到了一些数据渲染时的问题,下面对遇到的这些问题做下简单的介绍以及解决方法。我们都知道,在 Vue 中,组件通信的方式有很多,而针对那些没有父子级关系或者任何关系的组件要实现通信(数据传递)的方法就是使用 Vuex 或者 eventBus(事件总线),具体应该选择哪一种还要看实际情况。公司的项目中两种方法都有使用,而自己当时对 Vuex 的使用还不是很熟悉,所以就选择了 eventBus 来实现组件通信。然而在开发过程中还是遇到了一些问题。

  首先来看下项目中要实现的效果:

大致需求是这样:左边地图组件和右边视图组件需要关联,点击左边时需要发送 机器告警(图)的数据 和 机器缺陷(图)的数据 给视图组件,而,右边视图组件中有一个公共的组件用来展示数据,用户通过点击 “机器告警” 或者 “机器缺陷” 按钮来获得对应的图示。

robotState.vue 中的 html 代码(部分):

<template>
<div>
  <section class="the-wrapper">
    <h2 class="title">
      <span @click="switchView" :class="xxxx"></span>
      <span class="switch-btn" :class="{ active: currentTab === 1 }" @click="switchTab(1)">机器告警</span>
      <span class="switch-btn" :class="{ active: currentTab === 2 }" @click="switchTab(2)">机器缺陷</span>
    </h2>
  </section>
 <!-- 机器告警图(表) -->
 <section v-if="currentTab === 1" :key="0" style="xxxx">
   <transition enter-active-class="animated slideInLeft">
     <v-canvas-pie v-if="isPieView" ref="pie" />
     <v-table v-else ref="table" />
   </transition>
 </section>
  <!-- 机器缺陷图(表) -->
 <section v-if="currentTab === 2" :key="1" style="xxxx">
   <transition enter-active-class="animated slideInLeft">
     <v-canvas-pie v-if="isPieView" ref="pie" />
     <v-table v-else ref="table" />
   </transition>
 </section>
</div>
</template>

map.js 中的逻辑(部分):

getData = async city => {
  // 机器告警(图)
  axios.post('xxxxxxxxxxxxx',{id:city.code})
      .then(res=>{
         res.data.map(item=>{
             Bus.$emit('AlarmInfoPie',[
              { name:"正常",value:item.normal },
              { name:"一般",value:item.commonly },
              { name:"严重",value:item.serious },
             { name:"危急",value:item.critical },
            ])
        }) 
     })

 // 机器缺陷(图)
 axios.post('mmmmmmmmmmmmmmm',{id:city.code})
 .then(res=>{
     res.data.map(item=>{
         Bus.$emit('defectPie',[
          { name:"无缺陷",value:item.normal },
          { name:"一般缺陷",value:item.commonly },
          { name:"严重缺陷",value:item.serious },
          { name:"危急缺陷",value:item.critical },
         ])
     }) 
 })
}

robotState.vue 中的逻辑(部分):

export default {
  components:{
      VCanvasPie,
      VTable
  },
  data(){
      return {
          isPieView:true,
          currentTab:1,
     }
 },
 methods: {
     // 机器告警(图)
     async getRobotAlarmPie(){ 
         let temp,       // 定义需要渲染的数据
         const result = await axios.get('xxxxxxxxxxxxxxxx')
         result.data.map(item=>{
             // 数据处理(省略)
             this.$refs.pie.init(temp)
         })
         // 渲染从 map.js 传过来的数据
         Bus.$on('AlarmInfoPie',(data)=>{
             temp = data
         })
         this.$refs.pie.init(temp)
     },
      // 机器缺陷(图)
     async getRobotDefectPie(){
         let temp,   // 定义需要渲染的数据
         const result = await axios.get('xxxxxxxxxxxxxxxx')
         result.data.map(item=>{
             // 数据处理(省略)
             this.$refs.pie.init(temp)
         })
         // 渲染从 map.js 传过来的数据
         Bus.$on('defectPie',(data)=>{
             temp = data
         })
         this.$refs.pie.init(temp)
     }
 },
};

  一开始数据处理逻辑就是这样,结果却是:点击地图组件的某个区域时,会进行正常渲染 “机器告警” 的数据,如果点击 “机器缺陷” 时,再点击地图组件的当前区域时却得不到对应的数据,而是渲染的是 “机器告警” 的数据,当时很懵逼,最后才知道由于 右边视图组件共用了一个数据展示的组件,当点击地图某个区域时,map.js 中的 eventBus 会有两个事件发射,robotState.vue 中对应也就有两个事件监听,从而获取数据,无论是点击 “机器缺陷” 还是 “机器告警”,后面监听的事件得到的数据都会被覆盖。找到了问题原因就很好解决,最先想到的是使用 vuex 来解决,但是需要推翻以前的代码重写,因为项目中有很多数据展示,就很麻烦;然后想的是能不能使用 eventBus 中的方法来解决,最后找到了 eventBus.$off() 方法来解决。

  大致解决方法就是这样,可能解释的不是太清楚,自己可以写个案例实现下应该可以帮助理解。最后对 eventBus 的使用做一个小的补充,就是我们在项目中可能会创建多个 eventBus.js 文件,而两个或者多个组件要使用 eventBus 进行通信时一定要使用同一个 eventBus.js 文件,否则会报错,我之前就遇到过,哈哈!

  总结:Vue中使用 eventBus 实现组件间的通信时,需要对 Bus.$emit()、Bus.$on()、Bus.$off() 或者其他 API 搭配使用。结合当前环境,判断是否有干扰项从而决定是否对 eventBus 进行移除。