【11.全局事件总线(Global Event Bus)
】
全局事件总线:实现任意组件间通信
【原理图】
-
结合上图,假若C组件想要传递数据给A组件,那么,就通过全局事件总线在A组件中绑定
【$on】
一个自定义事件demo,并界定一个参数来接收传递的数据,同样在C组件中,就需要通过全局事件总线对自定义事件demo进行触发【$emit】
,并传递参数,这样就实现了任意组件之间的通信。 -
简单理解,全局事件总线其实就是一个中间介质,组件间的相互通信借助于这个中间介质,通过这个中间转换介质,从而完成数据的传递与接收,实现组件间的相互通信。
-
全局事件总线是一个独立存在的部分,要想实现组件间的相互通信,又是自定义事件,那就要满足两个条件:
【1】. 所有的组件都能访问到全局事件总线
【X总线】
【2】. 可以调用
$on【绑定】,$off【解绑】,$emit【触发事件】
全局事件总线应该安装在哪里呢?
- 安装全局事件总线前需要考虑:如果要所有组件对象【vm,vc】都能够获取到事件总线
- 从下图可以看出,当组件实例化对象vc获取数据时,会首先找到组件原型对象,如果该组件实例对象中没有此数据,那么就找到Vue的原型对象,Vue的原型对象只有一个,所有组件都能够获取到它的数据,所以事件总线要安装到Vue的原型对象上。
结合组件的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype【Vue的原型对象】
这个关系在作用就在于可以让 组件实例对象(vc,vm) 可以访问到Vue原型对象【Vue.prototype】上的属性和方法
-
回顾知识:
关于VueComponent:
1.A、B、C子组件本质上是一个名为VueComponent的构造函数,且不是程序员定义的,是调用Vue.extend()生成的
2.只需要写
<A/>或<A></A>
,Vue解析时会帮我创建A组件的实例对象,3.每次调用Vue.extend,返回的都是一个全新的VueComponent
-
1. 由于是在入口文件main.js中引入的Vue,所以事件总线需要配置到main.js当中
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
// 生命周期钩子beforeCreate中模板未解析,且this是vm
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线$bus
}
})
- 为什要在Vue实例对象中采用生命周期钩子beforeCreate安装事件总线?
- 由生命周期beforeCreate此时只是完成了初始化工作,创建了Vue实例对象,但是模板并没有解析,数据代理并没有运转。
- 2. 接下来,我们就要对想要接收到数据的组件进行自定义事件的绑定,简单来说就是,谁【比如:A】要接收数据,自定义事件就绑定在谁【比如:A】身上 【$on()里面要是用箭头函数】
- 绑定全局事件总线
mounted(){
// 绑定自定义事件
this.$bus.$on('自定义事件名', (接收参数)=>{
console.log('我是TestB组件,收到了数据', 接收参数);
})
}
- 3. 最后一步,全局事件总线的触发,事件的触发是在发送数据的组件中完成的,简单来说,谁【比如:B】是数据的发送者,谁【比如:B】就来触发事件
- 触发全局事件总线
methods:{
// 触发事件,事件名不能重复
触发事件方法名(){
this.$bus.$emit('自定义事件名', 传递参数);
}
},
- 4. 在得到数据之后,解绑事件,提高性能,在beforedestroy生命周期钩子中,用**$off**解绑当前组件所用到的数据
- 解绑自定义事件
// 销毁对应自定义事件
beforeDestroy(){
this.$bus.$off('自定义事件名')
}
完整代码:
School.vue组件
<template>
<div class="school">
<h1>School组件</h1>
<!-- 给子组件的实例对象VC绑定了事件getName,该事件触发会调用函数getStudentName -->
<h2>兄弟组件Student传过来的name:{{ StudentName }}</h2>
<h2>兄弟组件Student传过来的age:{{ StudentAge }}</h2>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
StudentName: '',
StudentAge: '',
};
},
mounted() {
// 绑定自定义事件haha
this.$bus.$on('haha', (...params) => {
console.log('School组件里的', this);
this.StudentName = params[0];
this.StudentAge = params[1];
console.log('我是School组件,收到了数据', params);
});
},
// 销毁对应自定义事件
beforeDestroy() {
// 解绑自定义事件
this.$bus.$off('haha')
},
};
</script>
<style scoped>
.school {
background-color: rgb(73, 192, 150);
}
</style>
...params
:可以以数组的形式接收多个参数。
Student.vue组件
<template>
<div class="student">
<h1>Student组件信息</h1>
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>学生年龄:{{ age }}</h2>
<h2>学生成绩:{{ score }}</h2>
<button class="haha" @click="sendStudentlName">
<h2>点击此处给兄弟组件School传值</h2></button>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
name: '何大春',
sex: '男',
age: '22',
score: '88',
};
},
methods: {
sendStudentlName(){
console.log("Student组件里的",this.name);
// 传给School组件两个参数
this.$bus.$emit('haha',this.name,this.age)
}
},
};
</script>
<style lang="less" scoped>
.student {
background-color: tomato;
padding: 50px;
margin-top: 50px;
margin-left: 50px;
width: 300px;
height: 350px;
}
.h2 {
padding: 5px;
margin: 5px 5px 5px 5px;
}
.haha {
background-color: rgb(211, 233, 130);
}
</style>
结果展示:
补充知识点:
问题1:“全局事件总线”需要哪些特点?
- 1)被所有组件(vc、vm)能够看得见
- 2)能够调用
$on、$emit、$off
问题2:“Vue原型对象上面的所有属性和方法是给谁用的?
- 是给所有的vm和vc使用的
问题3:为什么定义“全局事件总线”要放在main.js文件中?
- 因为哪里引入Vue,哪里才会去定义“全局事件总线”
问题4:“为什么定义“全局事件总线”要放在beforeCreate的钩子函数中?
1)beforeCreate钩子函数里this指代new出来的vm
2)在beforeCreate钩子函数里模板还没解析,数据监测和数据代理也还没完成呢。也就是说借助这个beforeCreate钩子函数你把想做的事儿做好了,原型上该放的放好了,随后模板开始解析,等组件执行的时候你该放的都放好了,后续才做都不会产生影响。
问题5:如何避免在使用“全局事件总线”时自定义函数名重名使用问题?比如组件1使用自定义函数名叫demo,那组件2不全文搜索也使用了自定义函数名也叫demo,这就混了
- 真实项目中src目录下创建一个config文件夹,里面创建个constants常量文件,里面用来定义要使用的自定义函数名,方便别人查看并避免重名问题。
问题6:“为什么要在组件销毁之前,把“全局事件总线”中定义的自定义事件函数解绑?那“知识点组件自定义事件”中咋没说解绑的事儿呢?
- “组件自定义事件”中,组件销毁了== vc销毁了,vc销毁了自定义事件也就销毁了,而“全局事件总线”中定义的自定义事件是一直存在的,哪怕使用组件销毁了,但是Vue实例定义的“全局事件总线”中还是会存在自定义事件,所以需要在组件销毁之前进行解绑。
注意7:销毁“全局事件总线”中定义的自定义事件请放在beforeDestroy()钩子中
注意8:子组件中使用“全局事件总线”时this.$bus.$on()
中回调配置要使用箭头函数,不要使用普通函数,普通函数中this指代vue实例,而箭头函数中this才指代vc
,因为最终要在school组件上接收平行组件发过来的消息,所以要使用vc,而不是要使用vue实例,因为vue实例不是我们最终要的。
mounted(){
// 绑定自定义事件
this.$bus.$on('自定义事件名', (接收参数)=>{
console.log('我是TestB组件,收到了数据', 接收参数);
})
},
mounted() {
// 绑定自定义事件haha
this.$bus.$on('haha', (...params) => {
console.log('School组件里的', this); //this = vc
this.StudentName = params[0];
this.StudentAge = params[1];
console.log('我是School组件,收到了数据', params);
});
},
总结:
全局事件总线实现了任意组件间的通信,有效简化了开发过程中一些数据传递的操作,同时也提高了程序的性能,但是值得注意的是,全局事件总线之所以叫全局,是因为任何组件都可以访问,这就导致如果大量组件都绑定了全局事件总线,难免会造成代码混乱,且自定义事件名可能发生重复的问题,所以在开发中,使用全局事件总线时要根据实际业务情况进行选择。
-
全局事件总线:一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
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去解绑当前组件所用到的事件。
olor:red">回调留在A组件自身。
```js
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
```
-
提供数据:
this.$bus.$emit('xxxx',数据)
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。