天天看点

全局事件总线

文章目录

  • ​​原理分析​​
  • ​​代码实现​​
  • ​​代码优化​​
  • ​​总结​​

原理分析

全局事件总线能实现任意两个组件间的通信

例如我们现在有如下的结构:

全局事件总线

我们想要实现任意两组件的通信,少不了一个中介X(图中右上角的粉色圆圈)。例如现在D组件要向A组件传一点数据,过程如下:

  • 在A组件里面写点代码,给X绑定一个自定义事件(假设这个自定义事件的名称和叫做demo)
  • 在D中写一段代码去触发X中的demo事件
  • 在触发事件的同时带点数据过去(比如说我们带个666)
  • 此时数据就以参数的形式来到了A组件中

在这个过程中,我们不难发现这个类似于中介的X,是有一定的要求的:

  • 他能被其他所有组件给看见
  • 这个X能调用​

    ​$on(),$off(),$emit()​

    ​等方法

首先我们可以讨论第一个要求怎么实现,我们有如下几种方法:

  • 借用浏览器对象window,所有的组件对它一定是可视的(此方法可行,但不推荐)
  • 既然对所有的组件对象可见,那么放在VueComponent构造函数的原型对象上(这个方法不可行,因为VueComponent构造函数是通过Vue.extend得来的,而每次得到的都是不同的VueComponent)
  • 在上一个方法的基础上,修改源码,每出现一个新的VueComponent,就挂载一个X(此方法可行,但不推荐)
  • 利用关系​

    ​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来进行标识

也就是说如上的代码我们应该写成:

全局事件总线

还有一个注意点:在组件销毁之前,我们应该把其绑定在全局事件总线上的事件给解绑!

在自定义事件中我们说解绑这个操作是可有可无的,因为当组件销毁死亡之后,其身上的自定义事件自然就会消失。但是这里的全局事件总线他是一直存在的,即使有一个组件销毁了,但是绑定在全局事件总线上的事件还在,这就会造成资源的浪费。所以在这里我们推荐:在组件销毁之前,我们应该把其绑定在全局事件总线上的事件给解绑
<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>      

总结

  1. 一种组件间通信的方式,适用于任意组件间通信。
  2. 安装全局事件总线:
new Vue({
    ......
    beforeCreate() {
        Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    },
    ......
})      
  1. 使用事件总线:
  1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
  demo(data){......}
}
......
mounted() {
  this.$bus.$on('xxxx',this.demo)
}      
  1. 提供数据:​

    ​this.$bus.$emit('xxxx',数据)​

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

继续阅读