作為一名使用Vue的前端開發者,有時候會聽到事件總線(EventBus)這個名詞。但可能是我入行比較晚,我在Vue網站中并沒有看到過事件總線的介紹,在項目中也沒有使用過。那究竟什么是事件總線?事件總線可以解決什么問題?
事件總線簡介
事件總線是一種組件通信方式,用于在工程的中的任意組件中進行事件觸發和數據傳遞。
通過在全局創建一個事件總線,所有組件(無論他們的關系是父子還是兄弟還是不相關)都可以使用同一個總線發送事件和監聽事件,傳輸數據。這樣通信就可以不受組件間關系限制,實現靈活的通信能力。
Vue2實現事件總線
創建總線
首先創建一個Vue2項目,可以使用Vue CLI
。然后在src/main.js
中創建一個事件總線。創建的方式有兩種:
- 新創建一個Vue實例
import Vue from 'vue'
import App from './App.vue'Vue.prototype.$EventBus = new Vue()
new Vue({ render: h => h(App), }).$mount('#app')
- 使用已有的Vue實例
import Vue from 'vue'
import App from './App.vue'new Vue({render: h => h(App),beforeCreate() { Vue.prototype.$EventBus = this; }
}).$mount('#app')
觸發/接收事件
我們假設有兩個組件A和B,A觸發事件,B接收事件。
- 組件A
<template><div> <p @click="add"> 點擊增加 </p> </div>
</template>
<script>
export default {name: 'Add',data() { return { sum: 1, addNum: 1 } },methods: {add() {this.sum += this.addNum;this.addNum++;this.$EventBus.$emit('add', this.sum);}}
}
</script>
- 組件B
<template><div> <h1>收到數據: {{ sum }}</h1> </div>
</template>
<script>
export default {name: 'HelloWorld',data() { return {sum: 1,listenFun: (sum) => { this.sum = sum; }}},mounted() {this.$EventBus.$on('add', this.listenFun)},beforeDestroy() {this.$EventBus.$off('add', this.listenFun)},
}
</script>
可以看到事件總線的實現方式實際上非常簡單,就是把一個Vue實例掛載為一個全局屬性,在這個實例上觸發事件,監聽事件即可。如果不需要監聽時,要記得銷毀監聽事件。
其它組件通信方式
Vue2有很多組件間的通信方式,這里總結一下:
- 組件Props 父組件向子組件傳遞數據
- 組件事件Emit 子組件觸發事件;父組件監聽事件,接收數據
- 組件v-model 通過props和事件實現父子組件數據的雙向綁定
- 依賴注入 父組件向后代組件傳遞數據
- Attributes 沒有被組件聲明為props或emits的屬性;父組件向子組件傳遞數據
- 狀態管理 全局共享的數據管理,一般使用Pinia或者Vuex等工具
- 事件總線 全局組件共享的事件管理
- 模板引用ref 父組件主動調用子組件方法,可傳遞數據
- 其它方式 可以存放數據的公共位置,比如Storage, Window等。
事件總線的優缺點
通過事件總線的實現,我們可以了解到事件總線可以非常簡單的實現全局組件共享的事件管理,傳遞數據等。既然如此簡單,那Vue為什么沒有推薦作為官方的組件通信方式?為什么即使Vue官方并無推薦,但卻有很多開發者使用事件總線。我們結合上面的其它組件通信方式,來討論下事件總線的優缺點。
優點
- 實現全局任意組件共享的數據傳輸
查看上面的通信方式,我們可以看到Vue提供的大部分方式都有組件關系的限制,大部分是父組件向子組件向后代組件之間傳遞。而事件總線卻沒有任何限制。 - 實現非常簡單
使用狀態管理工具也可以實現數據傳遞,但是這些工具都要引入依賴庫,有自己的使用方式。雖然并不麻煩,但是都沒有事件總線使用這么簡單。 - 全局的事件管理器
組件通信除了傳遞數據,另一個作用是實時觸發事件,針對事件進行操作。查看上面的組件通信方式,我們發現除事件總線外,全局的通信只是數據的傳遞,沒有事件的觸發。通過監聽狀態管理和Storage數據等,可以變相實現事件的管理,但是并沒有事件總線清晰和直接。
缺點
- 事件監聽只能被動接收數據,不能隨時獲取狀態
如果需要隨時獲取狀態,顯然還是狀態管理工具更適合。 - vue3不提供事件總線能力
在vue3中$on $off
等實例方法已被移除,組件實例不再實現事件觸發接口。官方推薦使用 mitt 等外部工具。
還有使用不慎帶來的很多問題。例如:
- 事件名共享同一個命名空間
- 不銷毀事件監聽器
如果在不使用后忘記銷毀事件監聽器,會造成難以排查的Bug或者引發性能問題。 - 誤銷毀同名事件其它監聽器
比如多個組件都監聽了同一事件’add’。其中某個組件銷毀了’add’事件下的所有監聽器this.$EventBus.$off('add')
,就會影響其他的組件。 - 其它問題 例如調試困難,耦合性高等等。
總結
事件總線作為一種全局的組件通信方法,符合訂閱發布模式,由于其簡單有效的使用方式,受到部分開發者的歡迎。但是由于各種使用不慎和維護帶來的問題,官方和許多開發者也不推薦使用:
在絕大多數情況下,不鼓勵使用全局的事件總線在組件之間進行通信。雖然在短期內往往是最簡單的解決方案,但從長期來看,它維護起來總是令人頭疼。根據具體情況來看,有多種事件總線的替代方案. —— Vue3遷移指南
但是事件總線就完全不能使用么?也并不是。首先上面說的各種使用問題,可以通過預先制定開發規范+嚴格代碼審核解決。其次,查看上面的通信方法,其實Vue并沒有直接的全局事件通知方式,作為一種全局事件通知工具,還是有它獨特的作用的。是否可以使用,還是要具體問題具體分析。
參考
- Vue3遷移指南 事件API
https://v3-migration.vuejs.org/zh/breaking-changes/events-api.html - mitt —— Tiny 200b functional event emitter / pubsub.
https://github.com/developit/mitt