文章目錄
- 原理分析
- 代碼實作
- 代碼優化
- 總結
原理分析
全局事件總線能實作任意兩個元件間的通信
例如我們現在有如下的結構:
我們想要實作任意兩元件的通信,少不了一個中介X(圖中右上角的粉色圓圈)。例如現在D元件要向A元件傳一點資料,過程如下:
- 在A元件裡面寫點代碼,給X綁定一個自定義事件(假設這個自定義事件的名稱和叫做demo)
- 在D中寫一段代碼去觸發X中的demo事件
- 在觸發事件的同時帶點資料過去(比如說我們帶個666)
- 此時資料就以參數的形式來到了A元件中
在這個過程中,我們不難發現這個類似于中介的X,是有一定的要求的:
- 他能被其他所有元件給看見
- 這個X能調用
等方法$on(),$off(),$emit()
首先我們可以讨論第一個要求怎麼實作,我們有如下幾種方法:
- 借用浏覽器對象window,所有的元件對它一定是可視的(此方法可行,但不推薦)
- 既然對所有的元件對象可見,那麼放在VueComponent構造函數的原型對象上(這個方法不可行,因為VueComponent構造函數是通過Vue.extend得來的,而每次得到的都是不同的VueComponent)
- 在上一個方法的基礎上,修改源碼,每出現一個新的VueComponent,就挂載一個X(此方法可行,但不推薦)
- 利用關系
,也就是說我們把X放在Vue.prototype身上,那麼所有的元件都可以搜尋到他。(此法推薦)VueComponent.prototype.__proto__ === 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>
總結
- 一種元件間通信的方式,适用于任意元件間通信。
- 安裝全局事件總線:
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去解綁目前元件所用到的事件。