全局事件总线【GlobalEventBus】🔥🔥🔥
1. 原理图
2. 成为x的条件
- 所有组件都能看到x
- x要能够调用$on、$off、$emit几个API,分别用于绑定、解绑、触发事件
探究一:将x放在哪能满足所有组件都能看到x这个条件?
可以放在window上,但这样不好,也可以修改源码,在每次创建VueComponent之后加载原型对象身上,但这样也不好,只能放在Vue的原型对象上
探究二:如何才能让x能够调用$on、$off、$emit这几个API?
$on、$off、$emit这几个API是在Vue的原型对象身上的,只有vm和vc可以访问到
往Vue的原型对象身上放一个变量,这个变量所有的组件都能看得到,另外,$on、$off、$emit这几个API是在Vue的原型对象身上的,只有vm和vc可以访问到,所以这个变量还得是vm或者vc
3. 安装全局事件总线的几种方式
1)向Vue的原型对象身上放一个变量x,值是一个vc【组件实例对象】
//main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 定义一个组件
const X = Vue.extend({})
// 实例化该组件
const vc = new X()
// 将该组件放到Vue原型对象身上的x变量中
Vue.prototype.x = vc
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
2)向Vue的原型对象身上放一个变量x,值是vm
使用vm的好处是默认就会实例化一个vm,不需要单独去造一个vc出来,更方便简洁
但以下的安装方式是错误的
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 实例化Vue之前安装全局事件总线
// 安装失败,此时vm还不可用
Vue.prototype.x = vm
//创建vm
const vm = new Vue({
el:'#app',
render: h => h(App)
})
// 不能实例化Vue后再安装全局事件总线,实例化Vue后,各组件就已经加载完毕了
// 如果有的组件在全局事件总线上挂载事件是在加载阶段【比如 mounted钩子中】进行的,会导致这些组件挂载事件不成功
Vue.prototype.x = vm
以下的安装方式才是正确的
//引入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
Vue.prototype.x = this
}
})
3)第二种方式还可以规范化,x通常写为$bus
//引入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
Vue.prototype.$bus = this
}
})
4. 如何正确的使用全局事件总线
以下操作是在安装全局事件总线后进行的
确定数据接收者和数据发送者,数据接收者绑定事件,数据发送者触发事件
// 父组件要接收数据
// 一般在mounted钩子中进行全局事件总线中事件的绑定
mounted() {
// 这里的函数引用可以直接写成函数,不过要写成箭头函数
this.$bus.$on('getIsSelected', this.getIsSelected)
this.$bus.$on('del', this.del)
},
// beforeDestroy钩子中记得解绑事件
beforeDestroy() {
this.$bus.$off(['getIsSelected','del'])
}
// 子组件中作相应事件的触发即可
methods:{
isSelected(newValue){
this.$bus.$emit('getIsSelected',newValue)
},
delTodo(id){
if(confirm('确定要删除吗?')) this.$bus.$emit('del',id)
}
}
5. Tip
- 全局事件总线并不是一项新的技术,而是总结的一项经验
- 全局事件总线中全局两字的含义:vm和所有的vc都能看得到
- 由于全局事件总线所有的vc和vm都能看得到,各个组件在通信时使用的事件名必须是唯一的,否则就会冲突,所以实际开发中,通常会单独建立一个常量文件,事件名就从这个常量文件中取,这样就避免了事件名冲突
- 组件销毁前,需要解绑当前组件向全局事件总线中绑定的事件,否则就是占用资源,因为只有这个事件可以精确解绑
- 如果从Vue开发者工具中看到触发事件的源头是Root组件,大概率该事件是绑定在全局事件总线上的
6. 总结
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
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去解绑当前组件所用到的事件。
遇到一个问题:
为什么不能将x放到VueComponent的原型对象上?
每定义一个组件,就会生成一个新的VueComponent,两个不同的VueComponent构造函数的原型对象是同一个吗?