Vue组件通信中事件总线(eventBus)的使用
之前公司的项目中使用 eventBus 进行组件通信时遇到了一些数据渲染时的问题,下面对遇到的这些问题做下简单的介绍以及解决方法。
我们都知道,在 Vue 中,组件通信的方式有很多,而针对那些没有父子级关系或者任何关系的组件要实现通信(数据传递)的方法就是使用 Vuex 或者 eventBus(事件总线),具体应该选择哪一种还要看实际情况。公司的项目中两种方法都有使用,而自己当时对 Vuex 的使用还不是很熟悉,所以就选择了 eventBus 来实现组件通信。然而在开发过程中还是遇到了一些问题。
首先来看下项目中要实现的效果:
大致需求是这样:左边地图组件和右边视图组件需要关联,点击左边时需要发送 机器告警(图)的数据 和 机器缺陷(图)的数据 给视图组件,而,右边视图组件中有一个公共的组件用来展示数据,用户通过点击 “机器告警” 或者 “机器缺陷” 按钮来获得对应的图示。
robotState.vue 中的 html 代码(部分):
1 <template>
2 <div>
3 <section class="the-wrapper">
4 <h2 class="title">
5 <span @click="switchView" :class="xxxx"></span>
6 <span class="switch-btn" :class="{ active: currentTab === 1 }" @click="switchTab(1)">机器告警</span>
7 <span class="switch-btn" :class="{ active: currentTab === 2 }" @click="switchTab(2)">机器缺陷</span>
8 </h2>
9 </section>
10 <!-- 机器告警图(表) -->
11 <section v-if="currentTab === 1" :key="0" style="xxxx">
12 <transition enter-active-class="animated slideInLeft">
13 <v-canvas-pie v-if="isPieView" ref="pie" />
14 <v-table v-else ref="table" />
15 </transition>
16 </section>
17 <!-- 机器缺陷图(表) -->
18 <section v-if="currentTab === 2" :key="1" style="xxxx">
19 <transition enter-active-class="animated slideInLeft">
20 <v-canvas-pie v-if="isPieView" ref="pie" />
21 <v-table v-else ref="table" />
22 </transition>
23 </section>
24 </div>
25 </template>
map.js 中的逻辑(部分):
1 getData = async (city) => {
2 // 机器告警(图)
3 axios.post('xxxxxxxxxxxxx',{id:city.code})
4 .then(res=>{
5 res.data.map(item=>{
6 Bus.$emit('AlarmInfoPie',[
7 { name:"正常",value:item.normal },
8 { name:"一般",value:item.commonly },
9 { name:"严重",value:item.serious },
10 { name:"危急",value:item.critical },
11 ])
12 })
13 })
14
15 // 机器缺陷(图)
16 axios.post('mmmmmmmmmmmmmmm',{id:city.code})
17 .then(res=>{
18 res.data.map(item=>{
19 Bus.$emit('defectPie',[
20 { name:"无缺陷",value:item.normal },
21 { name:"一般缺陷",value:item.commonly },
22 { name:"严重缺陷",value:item.serious },
23 { name:"危急缺陷",value:item.critical },
24 ])
25 })
26 })
27 }
robotState.vue 中的逻辑(部分):
1 export default {
2 components:{
3 VCanvasPie,
4 VTable
5 },
6 data(){
7 return {
8 isPieView:true,
9 currentTab:1,
10 }
11 },
12 methods: {
13 // 机器告警(图)
14 async getRobotAlarmPie(){
15 let temp, // 定义需要渲染的数据
16 const result = await axios.get('xxxxxxxxxxxxxxxx')
17 result.data.map(item=>{
18 // 数据处理(省略)
19 this.$refs.pie.init(temp)
20 })
21 // 渲染从 map.js 传过来的数据
22 Bus.$on('AlarmInfoPie',(data)=>{
23 temp = data
24 })
25 this.$refs.pie.init(temp)
26 },
27 // 机器缺陷(图)
28 async getRobotDefectPie(){
29 let temp, // 定义需要渲染的数据
30 const result = await axios.get('xxxxxxxxxxxxxxxx')
31 result.data.map(item=>{
32 // 数据处理(省略)
33 this.$refs.pie.init(temp)
34 })
35 // 渲染从 map.js 传过来的数据
36 Bus.$on('defectPie',(data)=>{
37 temp = data
38 })
39 this.$refs.pie.init(temp)
40 }
41 },
42 };
一开始数据处理逻辑就是这样,结果却是:点击地图组件的某个区域时,会进行正常渲染 “机器告警” 的数据,如果点击 “机器缺陷” 时,再点击地图组件的当前区域时却得不到对应的数据,而是渲染的是 “机器告警” 的数据,当时很懵逼,最后才知道由于 右边视图组件共用了一个数据展示的组件,当点击地图某个区域时,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 进行移除。