Vue里面的事件总线

865 阅读2分钟

大图

EventBus的简介

EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次

如何使用EventBus

在Vue的项目中怎么使用 EventBus 来实现组件之间的数据通讯呢?具体可以通过下面几个步骤来完成

初始化

首先你需要做的是创建事件总线并将其导出,以便其它模块可以使用或者监听它。我们可以通过两种方式来处理。先来看第一种,新创建一个 .js 文件,比如 event-bus.js

// event-bus.js


import Vue from 'vue'
export const EventBus = new Vue()

你需要做的只是引入 Vue 并导出它的一个实例(在这种情况下,我称它为 EventBus )。实质上它是一个不具备 DOM 的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便

另外一种方式,可以直接在项目中的 main.js 初始化 EventBus :

// main.js
Vue.prototype.$EventBus = new Vue()

注意,这种方式初始化的 EventBus 是一个 全局的事件总线 。稍后我们会花点时间专门聊一聊全局的事件总线。

现在我们已经创建了 EventBus ,接下来你需要做到的就是在你的组件中加载它,并且调用同一个方法,就如你在父子组件中互相传递消息一样

发送事件

假设你有两个子组件: DecreaseCountIncrementCount ,分别在按钮中绑定了 decrease()increment() 方法。这两个方法做的事情很简单,就是数值递减(增) 1 ,以及角度值递减(增) 180 。在这两个方法中,通过 EventBus.$emit(channel: string, callback(payload1,…)) 监听 decreasedincremented 频道。

<!-- DecreaseCount.vue -->
<template>
    <button @click="decrease()">-</button>
</template>

<script>
import { EventBus } from "../event-bus.js";
export default {
    name: "DecreaseCount",
    data() {
        return {
            num: 1,
            deg:180
        };
    },
    methods: {
        decrease() {
            EventBus.$emit("decreased", {
                num:this.num,
                deg:this.deg
            });
        }
    }
}; 
</script>

<!-- IncrementCount.vue -->
<template>
    <button @click="increment()">+</button>
</template>

<script>
import { EventBus } from "../event-bus.js";
export default {
    name: "IncrementCount",
    data() {
        return {
            num: 1,
            deg:180
        };
    },
    methods: {
        increment() {
            EventBus.$emit("incremented", {
                num:this.num,
                deg:this.deg
            });
        }
    }
};
 </script>

上面的示例,在 DecreaseCountIncrementCount 分别发送出了 decreasedincremented频道。接下来,我们需要在另一个组件中接收这两个事件,保持数据在各组件之间的通讯

接收事件

现在我们可以在组件 App.vue 中使用 EventBus.$on(channel: string, callback(payload1,…))监听 DecreaseCountIncrementCount 分别发送出了 decreasedincremented 频道。

<!-- App.vue -->
<template>
    <div id="app">
        <div class="container" :style="{transform: 'rotateY(' + degValue + 'deg)'}">
            <div class="front">
                <div class="increment">
                    <IncrementCount />
                </div>
                <div class="show-front"> {{fontCount}} </div>
                <div class="decrement">
                    <DecreaseCount />
                </div>
            </div>

            <div class="back">
                <div class="increment">
                    <IncrementCount />
                </div>
                <div class="show-back"> {{backCount}} </div>
                <div class="decrement">
                    <DecreaseCount />
                </div>
            </div> 
        </div>
    </div>
</template>

<script>
import IncrementCount from "./components/IncrementCount";
import DecreaseCount from "./components/DecreaseCount";
import { EventBus } from "./event-bus.js";
export default {
    name: "App",
    components: {
        IncrementCount,
        DecreaseCount
    },
    data() {
        return {
            degValue:0,
            fontCount:0,
            backCount:0
        };
    },
    mounted() {
        EventBus.$on("incremented", ({num,deg}) => {
            this.fontCount += num
            this.$nextTick(()=>{
                this.backCount += num
                this.degValue += deg;
            })
        });
        EventBus.$on("decreased", ({num,deg}) => {
            this.fontCount -= num
            this.$nextTick(()=>{
                this.backCount -= num
                this.degValue -= deg;
            })
        });
    }
}; 
</script>

如果你只想监听一次事件的发生,可以使用 EventBus.$once(channel: string, callback(payload1,…))

移除事件监听者

如果想移除事件的监听,可以像下面这样操作:

import { eventBus } from './event-bus.js'
EventBus.$off('decreased', {})

你也可以使用 EventBus.$off(‘decreased’) 来移除应用内所有对此事件的监听。或者直接调用EventBus.$off() 来移除所有事件频道, 注意不需要添加任何参数

事件总线使用时注意事项:

两个问题:

  • 问题1: 为什么第一次触发的时候页面B中的on事件没有被触发
  • 问题2: 为什么后面再一次依次去触发的时候会出现,每一次都会发现好像之前的on事件分发都没有被撤销一样,导致每一次的事件触发执行越来越多。

问题一

第一次触发的时候页面B中的on事件没有被触发

产生原因

当我们还在页面A的时候,页面B还没生成,也就是页面B中的 created 中所监听的来自于 A 中的事件还没有被触发。这个时候当你 A 中 emit 事件的时候,B其实是没有监听到的。

解决办法:

我们可以把 A 页面组件中的 emit 事件写在 beforeDestory 中去。因为这个时候,B 页面组件已经被 created 了,也就是我们写的 on 事件已经触发了,所以可以在 beforeDestory 的时候, on 事件已经触发了,所以可以在 beforeDestory 的时候,on 事件已经触发了,所以可以在 beforeDestory 的时候,emit 事件

问题二

后面再一次依次去触发的时候会出现,每一次都会发现好像之前的on事件分发都没有被撤销一样,导致每一次的事件触发执行越来越多。

产生原因

就是说,这个 $on 事件是不会自动清楚销毁的,需要我们手动来销毁。(不过我不太清楚这里的 external bus 是什么意思,有大神能解答一下的吗,尤大大也提到如果是注册的是 external bus 的时候需要清除)

解决办法:

在B组件页面中添加Bus.$off来关闭

// 在B组件页面中添加以下语句,在组件beforeDestory的时候销毁。
beforeDestroy () {
   bus.$off('get', this.myhandle)
}

总结

所以,如果想要用 bus 来进行页面组件之间的数据传递,需要注意两点:

一、组件A emit 事件应在beforeDestory生命周期内。

二、组件B内的 on 记得要销毁