所謂跨元件,即是兩個元件並無父子關係,並沒有被對方包著。如果要互相傳遞資料,可以使用 mitt(在 Vue 2 是 event bus)、Vuex 或 route props 來處理。
Event bus 和 Mitt 的原理是一樣,假設現在有兩個元件,A 元件和 B 元件。我想把 A 元件的資料傳給 B 元件,步驟就是:
on
來註冊監聽該事件off
把該監聽移除接下來這幾天,我會為以上每種方法,各自寫一篇解說文章。此篇文章會集中討論 mitt 與 eventbus。
先講解 Vue 2 的 eventbus 用法,因為懂了 eventbus 自然就會懂得用 mitt。注意,event bus 已經不能在 Vue 3 裏使用,因為 Vue 3 已移除 $on
、$off
、 $once
語法, 而 eventbus 需要使用 $on
來監聽 $emit
發出的事件,因此沒有 $on
的話,就無法用 event bus 的方法。
稍為重溫這些語法:
$on
:註冊監聽$off
:銷毁監聽$once
:只監聽一次$emit
:發出事件首先建立一個 event bus,之後我們會使用此 event bus 來監聽事件和觸發事件。建立 event bus 的常見做法有兩種,擇一即可:
第一種做法,就在 main.js 建立 event bus:
Vue.prototype.$bus = new Vue();
Vue.prototype
的語法是在 Vue 實體新增一個屬性。屬性前需要有 $
符號,這是官方提倡的一個寫法,用作標示這個屬性能夠在所有元件裏使用,同時避免與任何 data, computed 和 methods 的資料命名產生衝突。
第二種做法,就是直接另外建立新的 Vue 實體,把它當作 event bus。例如我在之後的示範例子中,會建立一個名為 eventBus.js 的檔案,並建立新的 Vue 實體。
eventBus.js
import Vue from "vue";
export const EventBus = new Vue();
如果是前者,在任何元件裏使用 event bus 時,只需要寫 this.$bus
來使用。後者就需要先把 event bus import
進來才可以使用:
import { EventBus } from "@/eventBus";
EventBus.$on(...);
$on
建立監聽事件,$emit
觸發事件假設現在有以下情景:
有兩個同階層的元件,Button.vue
和 Message.vue
,當我按下 Button.vue
的按鈕時,就會把在 Button.vue
的資料,傳送給 Message.vue
,Message.vue
會負責把資料顯示出來。
做法就是:
Button.vue
綁上 emit,當用戶按按鈕,就觸發 emit 來發出事件,並把資料一併帶出去。Message.vue
監聽 Button.vue
所發出的事件,當事件從 Button.vue
發出時,就接收資料,並把它塞到 data,再把它顯示出來。Button.vue:
<template>
<button type="button" @click="sendMessage">send message</button>
</template>
import { EventBus } from "@/eventBus";
export default {
data() {
return {
message: "Hello Vue!",
};
},
methods: {
sendMessage() {
// 發出 click-send-msg 事件,並把 message 當作參數傳出去
EventBus.$emit("click-send-msg", this.message);
},
},
};
Message.vue:
<template>
<p>Button 傳來的 Message: {{ msg }}</p>
</template>
<script>
import { EventBus } from "@/eventBus";
export default {
data() {
return {
msg: "",
};
},
mounted() {
// 監聽事件
EventBus.$on("click-send-msg", (msgdata) => (this.msg = msgdata));
},
};
</script>
$off
移除監聽當元件被銷毀時,建議在 beforeDestroy
hook 裏把此元件用到的監聽事件移除,保持網頁的效能。以下會再詳細解釋當中原因。
https://codesandbox.io/s/vue-2-event-bus-shi-fan-ewj4i?file=/src/components/Message.vue
這是因為當元件被銷毀,在此元件使用 event bus 所註冊的 event bus 監聽事件並不會隨之而消失。 更可怕的情況是,假設有兩個元件同時監聽同一個事件,即使其中一個元件被銷毀,剩下一個元件,但當該元件偵測到該事件時被觸發時,結果是網頁會發出兩個事件,不是一個。這是因為前一個被銷毀的元件仍然殘留監聽事件。
在此附上此文章提出的此例子。明顯示範了如果沒有移除監聽事件的話,會出現兩個問題:
建議大家把程式碼複製貼上到編輯器,以及使用本地 live server 預覽跑一次,一邊打開 console 和 memory 去看記憶體來理解作者的意思。
此例子所示範的情況是:
count
的數量來決定渲染多少個 BigTextComponent
。BigTextComponent
。BigTextComponent
在 created
hook 裏,都會產生一筆極長的陣列,模擬資料量很大。同時,此元件會監聽由 event 按鈕發出的事件,當它發出事件時,就會跑 console.log('QQ iDontKnow')
。BigTextComponent
所監聽的事件 ('myEvent')。如果我們不主動使用 $bus.$off('myEvent', this.eventHandler);
來移除元件的監聽,即使按下 minus 按鈕來銷毀元件,還是會殘留這些元件的監聽,造成以上提到的兩個問題:
console.log('QQ iDontKnow')
因此,需要主動在元件加入移除監視事件的程式碼來解決問題。
Vue 3 的 mitt 跟以上提到 Vue 2 所使用的 event bus 幾乎是一樣。不同的是,mitt 是一個插件,需要事先載入,就是因為 Vue 3 移除了 $on
和 $off
語法,因此要依賴 mitt 此插件來完成。
做法跟以上的步驟一樣,同樣是註冊監聽事件和建立觸發事件,在此就不再重複解說,直接分享完整的程式碼示範:
https://codesandbox.io/s/vue-3-mitt-shi-fan-ox46v?file=/src/main.js
補充一點,Vue 3 建議使用 beforeUnmount
,而非 beforeDestroy
。
$on
註冊監聽事件,同時在另一個元件使用 $emit
來觸發事件。beforeDestroy
(Vue 2)或 beforeUnmount
(Vue 3)移除監聽事件,保持網頁效能。How To Create a Global Event Bus in Vue 2
Vue3 中使用 Event Bus
Vue event bus 介紹
[Vue] Event Bus 是什麼? 怎麼用?
Data Pass Between Components using EventBus in Vue 3