Vue CLI 系列之(十二)全局事件总线

全局事件总线【GlobalEventBus】🔥🔥🔥 1. 原理图2. 成为x的条件所有组件都能看到x x要能够调用$on、$off、$emit几个API

全局事件总线【GlobalEventBus】🔥🔥🔥

1. 原理图

2. 成为x的条件

  1. 所有组件都能看到x
  2. 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

  1. 全局事件总线并不是一项新的技术,而是总结的一项经验
  2. 全局事件总线中全局两字的含义:vm和所有的vc都能看得到
  3. 由于全局事件总线所有的vc和vm都能看得到,各个组件在通信时使用的事件名必须是唯一的,否则就会冲突,所以实际开发中,通常会单独建立一个常量文件,事件名就从这个常量文件中取,这样就避免了事件名冲突
  4. 组件销毁前,需要解绑当前组件向全局事件总线中绑定的事件,否则就是占用资源,因为只有这个事件可以精确解绑
  5. 如果从Vue开发者工具中看到触发事件的源头是Root组件,大概率该事件是绑定在全局事件总线上的

6. 总结

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件

遇到一个问题:

为什么不能将x放到VueComponent的原型对象上?

每定义一个组件,就会生成一个新的VueComponent,两个不同的VueComponent构造函数的原型对象是同一个吗?

标签: 工具 绑定 加载