Vue事件總線(EventBus)

闡述一下 VUE中 eventbus 的原理

解答:
EventBus是消息傳遞的一種方式剖毯,基于一個消息中心,訂閱和發(fā)布消息的模式教馆,稱為發(fā)布訂閱者模式逊谋。
on('name', fn)訂閱消息,name:訂閱的消息名稱土铺, fn: 訂閱消息的函數(shù)(參數(shù)是接收的消息)
emit('name', args)發(fā)布消息, name:發(fā)布的消息名稱 胶滋, args:發(fā)布的消息

vue組件非常常見的有父子組件通信板鬓,兄弟組件通信。而父子組件通信就很簡單究恤,父組件會通過 props 向下傳數(shù)據(jù)給子組件俭令,當子組件有事情要告訴父組件時會通過 $emit 事件告訴父組件。如果兩個頁面沒有任何引入和被引入關(guān)系部宿,該如何通信了抄腔?

vuex?那如果項目沒那么復雜窟赏,不需要類似Vuex這樣的庫來處理組件之間的數(shù)據(jù)通信呢妓柜,這時候就可以考慮Vue中的 事件總線 ,即 EventBus來通信涯穷。
EventBus 又稱為事件總線棍掐。在Vue中可以使用 EventBus 來作為溝通橋梁的概念,就像是所有組件共用相同的事件中心拷况,可以向該中心注冊發(fā)送事件或接收事件作煌,所以組件都可以上下平行地通知其他組件,但也就是太方便所以若使用不慎赚瘦,就會造成難以維護的“災難”粟誓,因此才需要更完善的Vuex作為狀態(tài)管理中心,將通知的概念上升到共享狀態(tài)層次起意。

寫的時候可能會碰到一些問題鹰服,嘿嘿,注意下發(fā)布/訂閱時機揽咕,消息發(fā)布時悲酷,直接發(fā)布給(通知)訂閱者,如果沒有訂閱者亲善,就不發(fā)送设易。發(fā)布完畢了,再訂閱蛹头,沒用顿肺,只能等下次發(fā)布了,所以訂閱事件必須在發(fā)布之前渣蜗。

eg: A發(fā)布消息屠尊,B訂閱消息
所以B頁面要成功訂閱消息的話,必須保證先加載B頁面耕拷,然后再加載A知染,先把訂閱者$on加載在內(nèi)存中,否則拿不到消息斑胜。

我是鋼筋我就必須刷新后先進A再進B控淡。這就要先了解下Vue路由切換特性嫌吠,vue路由切換時,會先加載新的組件掺炭,等新的組件渲染好但是還沒有掛載前辫诅,銷毀舊的組件,之后掛載新組件涧狮。

新組件beforeCreate
        ↓
新組件created
        ↓
新組件beforeMount
        ↓
舊組件beforeDestroy
        ↓
舊組件destroyed
        ↓
新組件mounted

注意炕矮,在$emit時,必須已經(jīng)$on者冤,否則將無法監(jiān)聽到事件肤视。
所以正確的寫法應該是在需要接收值的組件的created生命周期函數(shù)里寫$on,在需要往外傳值的組件的destroyed生命周期函數(shù)函數(shù)里寫:

destroyed(){
  eventBus.$emit('dataUpdate',data)
}

這樣寫涉枫,在舊組件銷毀的時候新的組件拿到舊組件傳來的值邢滑,然后在掛載的時候更新頁面里的數(shù)據(jù)。

一愿汰、這種倆頁面跳轉(zhuǎn)完全可以通過router傳參解決困后,感覺忙活一圈沒卵用;
二衬廷、B摇予、C頁面訂閱,A發(fā)布吗跋,在進入B或者C時侧戴,上面那個destroyed的方法,必須是從A進B頁面跌宛,那進C時就沒了救鲤,而且也無法從mounted中直接獲取數(shù)據(jù)然后更改data數(shù)據(jù),有局限秩冈。
所以我感覺這個EventBus主要應用場景應該是,一個頁面中有多個組件斥扛,也可能組件套組件入问,這種情況用props和emit傳參太麻煩,用EventBus正好解決問題稀颁。

問:vue兄弟組件如何通訊芬失?
1、讓兄弟組件通過一個共同的父組件做為通訊橋梁彼此通訊(props匾灶、$emit)棱烂。
2、EventBus

廢話少說上代碼:
event-bus.js

import Vue from "vue";
export const EventBus = new Vue();

a.vue

<template>
    <button @click="sendMsg()">發(fā)送</button>
</template>

<script>
import { EventBus } from "@assets/utils/event-bus";
export default {
  methods: {
    sendMsg() {
      EventBus.$emit("aMsg", "來自A頁面的消息");
    }
  }
};
</script>

b.vue

<template>
    <p>B頁面阶女,A發(fā)送來的消息:{{ msg }}</p>
</template>

<script>
import { EventBus } from "@assets/utils/event-bus";
export default {
  data() {
    return {
      msg: "defaultMsg"
    };
  },
  mounted() {
    EventBus.$on("aMsg", msg => {
      // A發(fā)送來的消息
      this.msg = msg;
    });
  }
};
</script>

上面就是 EventBus 的使用方法颊糜,是不是很簡單哩治。但每次使用 EventBus 時都需要在各組件中引入 event-bus.js 。事實上衬鱼,我們還可以讓事情變得更簡單一些业筏。那就是創(chuàng)建一個全局的 EventBus 。

全局EventBus

main.js

...

var EventBus = new Vue();

Object.defineProperties(Vue.prototype, {
  $bus: {
    get: function() {
      return EventBus;
    }
  }
});
...

a.vue

<template>
  <button @click="sendMsg()">發(fā)送</button>
</template>

<script>
import { EventBus } from "@assets/utils/event-bus";
export default {
  methods: {
    sendMsg() {
      EventBus.$emit("aMsg", "來自A頁面的消息");
      this.$bus.$emit("nameOfEvent", {
        name: "shy"
      });
    }
  }
  // destroyed() {
  //   EventBus.$emit("aMsg", "來自A頁面的消息destroyed");
  //   this.$bus.$emit("nameOfEvent", {
  //     name: "shy-destroyed"
  //   });
  // }
};
</script>

b.vue

<template>
  <p>BBB頁面鸟赫,A發(fā)送來的消息:{{ msg }}</p>
</template>

<script>
import { EventBus } from "@assets/utils/event-bus";
export default {
  data() {
    return {
      msg: "defaultMsg"
    };
  },
  created() {
    EventBus.$on("aMsg", msg => {
      // A發(fā)送來的消息
      this.msg = msg;
    });
    this.$bus.$on("nameOfEvent", $event => {
      console.log($event);
    });
  }
};
</script>

插件掛載方式

1蒜胖、定義一個統(tǒng)一事件管理器

// 插件掛載方式
import Vue from 'vue'
// Bus 就是一個Vue對象,因此Vue.$on: 監(jiān)聽`當前實例`上的自定義事件
let Bus = new Vue()
let install = (Vue) => {
  Vue.prototype.$bus = Bus
}
export default { install }

2抛蚤、引用EventBus

import Vue from 'vue'
import Bus from '@/assets/js/eventBus'
Vue.use(Bus)

移除事件監(jiān)聽者

如果想移除事件的監(jiān)聽台谢,可以像下面這樣操作:

import { 
  eventBus 
} from './event-bus.js'
EventBus.$off('aMsg', {})

你也可以使用 EventBus.$off('aMsg') 來移除應用內(nèi)所有對此某個事件的監(jiān)聽∷昃或者直接調(diào)用 EventBus.$off() 來移除所有事件頻道朋沮,不需要添加任何參數(shù) 。

知識點

Vue.$on 監(jiān)聽 當前實例 上的自定義事件蒿偎。

事件可以由 vm.$emit 觸發(fā)朽们。回調(diào)函數(shù)會接收所有傳入事件觸發(fā)函數(shù)的額外參數(shù)诉位。

Vue.$off 移除自定義事件監(jiān)聽器骑脱。

vm.$off( [event, callback] )

如果沒有提供參數(shù),則移除所有的事件監(jiān)聽器苍糠;
如果只提供了事件叁丧,則移除該事件所有的監(jiān)聽器;
如果同時提供了事件與回調(diào)岳瞭,則只移除這個回調(diào)的監(jiān)聽器拥娄。

發(fā)布訂閱模式主要包含哪些內(nèi)容呢?

  1. 發(fā)布函數(shù),發(fā)布的時候執(zhí)行相應的回調(diào)
  2. 訂閱函數(shù)瞳筏,添加訂閱者,傳入發(fā)布時要執(zhí)行的函數(shù),可能會攜額外參數(shù)
  3. 一個緩存訂閱者以及訂閱者的回調(diào)函數(shù)的列表
  4. 取消訂閱(需要分情況討論)

自己實現(xiàn)一個 Observer 對象:

//用于存儲訂閱的事件名稱以及回調(diào)函數(shù)列表的鍵值對
function Observer() {
    this.cache = {}  
}
 
//key:訂閱消息的類型的標識(名稱)稚瘾,fn收到消息之后執(zhí)行的回調(diào)函數(shù)
Observer.prototype.on = function (key,fn) {
    if(!this.cache[key]){
        this.cache[key]=[]
    }
    this.cache[key].push(fn)
}
 
//arguments 是發(fā)布消息時候攜帶的參數(shù)數(shù)組
Observer.prototype.emit = function (key) {
    if(this.cache[key]&&this.cache[key].length>0){
        var fns = this.cache[key]
    }
    for(let i=0;i<fns.length;i++){
        Array.prototype.shift.call(arguments)
        fns[i].apply(this,arguments)
    }
}
// remove 的時候需要注意,如果你直接傳入一個匿名函數(shù)fn姚炕,那么你在remove的時候是無法找到這個函數(shù)并且把它移除的摊欠,變通方式是傳入一個
//指向該函數(shù)的指針,而 訂閱的時候存入的也是這個指針
Observer.prototype.remove = function (key,fn) {
    let fns = this.cache[key]
    if(!fns||fns.length===0){
        return
    }
    //如果沒有傳入fn柱宦,那么就是取消所有該事件的訂閱
    if(!fn){
        fns=[]
    }else {
        fns.forEach((item,index)=>{
            if(item===fn){
                fns.splice(index,1)
            }
        })
    }
}
 
//example
 
var obj = new Observer()
obj.on('hello',function (a,b) {
    console.log(a,b)
})
obj.emit('hello',1,2)
//取消訂閱事件的回調(diào)必須是具名函數(shù)
obj.on('test',fn1 =function () {
    console.log('fn1')
})
obj.on('test',fn2 = function () {
    console.log('fn2')
})
obj.remove('test',fn1)
obj.emit('test')

為什么會使用發(fā)布訂閱模式呢? 它的優(yōu)點在于:

  • 實現(xiàn)時間上的解耦(組件,模塊之間的異步通訊)
  • 對象之間的解耦,交由發(fā)布訂閱的對象管理對象之間的耦合關(guān)系.

引用:結(jié)合 Vue 源碼談談發(fā)布-訂閱模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末些椒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子掸刊,更是在濱河造成了極大的恐慌免糕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異石窑,居然都是意外死亡牌芋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門尼斧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姜贡,“玉大人,你說我怎么就攤上這事棺棵÷タ龋” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵烛恤,是天一觀的道長母怜。 經(jīng)常有香客問我,道長缚柏,這世上最難降的妖魔是什么苹熏? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮币喧,結(jié)果婚禮上轨域,老公的妹妹穿的比我還像新娘。我一直安慰自己杀餐,他們只是感情好干发,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著史翘,像睡著了一般枉长。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琼讽,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天必峰,我揣著相機與錄音,去河邊找鬼钻蹬。 笑死吼蚁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的问欠。 我是一名探鬼主播肝匆,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼溅潜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起薪伏,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤滚澜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嫁怀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體设捐,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡借浊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了萝招。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚂斤。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖槐沼,靈堂內(nèi)的尸體忽然破棺而出曙蒸,到底是詐尸還是另有隱情,我是刑警寧澤岗钩,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布纽窟,位于F島的核電站,受9級特大地震影響兼吓,放射性物質(zhì)發(fā)生泄漏臂港。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一视搏、第九天 我趴在偏房一處隱蔽的房頂上張望审孽。 院中可真熱鬧,春花似錦浑娜、人聲如沸佑力。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搓萧。三九已至,卻和暖如春宛畦,著一層夾襖步出監(jiān)牢的瞬間瘸洛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工次和, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留反肋,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓踏施,卻偏偏與公主長得像石蔗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子畅形,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容