全局事件总线能实现任意两个组件间的通信
例如我们现在有如下的结构:

我们想要实现任意两组件的通信,少不了一个中介X(图中右上角的粉色圆圈)。例如现在D组件要向A组件传一点数据,过程如下:
在这个过程中,我们不难发现这个类似于中介的X,是有一定的要求的:
$on(),$off(),$emit()等方法首先我们可以讨论第一个要求怎么实现,我们有如下几种方法:
VueComponent.prototype.__proto__ === Vue.prototype,也就是说我们把X放在Vue.prototype身上,那么所有的组件都可以搜寻到他。(此法推荐)接下来我们实现第二个要求:
$on(),$off(),$emit()等方法都存在于Vue的原型对象上。
因为Vue原型对象上的属性和方法都是给Vue的实例对象(vm)或组件的实例对象(vc)用的,所以这个X我们必须创建为Vue的实例对象或者组件的实例对象。
不过我们普遍强调只能由一个Vue的实例对象,所以我们一般使用组件的实例对象来代替这个中介X
例如我们现在有如下的组件结构:

我们想实现两个兄弟组件间的通信,Student组件将学生姓名交给School组件,步骤如下:
①首先创建好中介X
我们写在main.js中,因为Vue是在那个时候被引入的
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
const Demo = Vue.extend()
const d = new Demo()
Vue.prototype.talkHelper = d
new Vue({
render: h => h(App),
}).$mount('#app')
②接受者方(School组件)去在中介上绑定事件
School组件:
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<!-- <button @click="getSchoolName(name)">点击将学校名称交给父组件App</button>-->
<hr>
</div>
</template>
<script>
export default {
name:'DongBei',
data(){
return {
name:'NEFU',
address:'哈尔滨',
}
},
// props:['getSchoolName'],
methods:{
studentNameDeliver(name){
console.log('School组件已经接收到了来自Studennt组件的学生名称',name)
}
},
mounted() {
this.talkHelper.$on('studentName',this.studentNameDeliver)
}
}
</script>
<style >
.demo {
background-color: yellow;
}
</style>
③发送者方(Student组件)去调用中介身上的方法
Student组件:
<template>
<div class="demo">
<h2 class="stu" >学生名称:{{name}}</h2>
<h2 >学生年纪:{{age}}</h2>
<button @click="deliverStudentName">把学生名称给School组件</button>
</div>
</template>
<script>
export default {
name:'MyStudent',
data(){
return {
name:'张三',
age:18
}
},
methods:{
deliverStudentName(){
this.talkHelper.$emit('studentName',this.name)
}
}
}
</script>
<style >
/*.demo {*/
/* background-color: green;*/
/*}*/
</style>
最终的效果:


在此处单独创建一个组件实例对象vc太麻烦,我们不如就使用Vue的实例对象vm来作为这个中介。

不过这么写是有错误的。时机有点晚了!因为当前三行vm创建完毕之后,App组件整个都已经放在页面上去了,这就意味着School组件上的mounted都已经执行完了!

此时你再去Vue的原型对象上放一个中介已经晚了!
所以说这个中介放入的时机非常的重要,它既要在vm定义之后,又要在App组件放入之前。所以这个时候我们就想到了要用生命周期钩子!

我们选择使用beforeCreate(),这个时候模板还没有开始解析
于是我们改进后的代码如下:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// const Demo = Vue.extend()
// const d = new Demo()
//
// Vue.prototype.talkHelper = d
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.talkHelper = this
}
}).$mount('#app')
这个中介其实就是全局事件总线,我们一般用$bus来进行标识
也就是说如上的代码我们应该写成:
还有一个注意点:在组件销毁之前,我们应该把其绑定在全局事件总线上的事件给解绑!
在自定义事件中我们说解绑这个操作是可有可无的,因为当组件销毁死亡之后,其身上的自定义事件自然就会消失。但是这里的全局事件总线他是一直存在的,即使有一个组件销毁了,但是绑定在全局事件总线上的事件还在,这就会造成资源的浪费。所以在这里我们推荐:在组件销毁之前,我们应该把其绑定在全局事件总线上的事件给解绑
我们使用beforeDestroy()生命周期钩子来执行解绑操作:
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<hr>
</div>
</template>
<script>
export default {
name:'DongBei',
data(){
return {
name:'NEFU',
address:'哈尔滨',
}
},
methods:{
studentNameDeliver(name){
console.log('School组件已经接收到了来自Studennt组件的学生名称',name)
}
},
mounted() {
this.talkHelper.$on('studentName',this.studentNameDeliver)
},
beforeDestroy() {
this.talkHelper.$off('studentName')
}
}
</script>
<style >
.demo {
background-color: yellow;
}
</style>
全局事件总线(GlobalEventBus)
一种组件间通信的方式,适用于任意组件间通信。
安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
使用事件总线:
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
提供数据:this.$bus.$emit('xxxx',数据)
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。