天天看點

全局事件總線

文章目錄

  • ​​原理分析​​
  • ​​代碼實作​​
  • ​​代碼優化​​
  • ​​總結​​

原理分析

全局事件總線能實作任意兩個元件間的通信

例如我們現在有如下的結構:

全局事件總線

我們想要實作任意兩元件的通信,少不了一個中介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去解綁目前元件所用到的事件。

繼續閱讀