iT邦幫忙

2021 iThome 鐵人賽

DAY 12
1
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 12

不只懂 Vue 語法:如何用 event bus 或 mitt 實現跨元件傳遞資料?

  • 分享至 

  • xImage
  •  

問題回答

所謂跨元件,即是兩個元件並無父子關係,並沒有被對方包著。如果要互相傳遞資料,可以使用 mitt(在 Vue 2 是 event bus)、Vuex 或 route props 來處理。

Event bus 和 Mitt 的原理是一樣,假設現在有兩個元件,A 元件和 B 元件。我想把 A 元件的資料傳給 B 元件,步驟就是:

  1. 在 A 元件發出某個事件
  2. 在 B 元件使用 on 來註冊監聽該事件
  3. 當 B 元件銷毀時,使用 off 把該監聽移除

接下來這幾天,我會為以上每種方法,各自寫一篇解說文章。此篇文章會集中討論 mitt 與 eventbus。

Vue 2 的 event bus

先講解 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 來監聽事件和觸發事件。建立 event bus 的常見做法有兩種,擇一即可:

  1. 在原本的 Vue 實體裏,再建立一個 Vue 實體當作 event bus
  2. 建立一個新的 Vue 實體,當作 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.vueMessage.vue,當我按下 Button.vue 的按鈕時,就會把在 Button.vue 的資料,傳送給 Message.vueMessage.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
  • 你可以按 minus 來減少 count 的數值,也就是銷毀已建立的 BigTextComponent
  • 每個 BigTextComponentcreated hook 裏,都會產生一筆極長的陣列,模擬資料量很大。同時,此元件會監聽由 event 按鈕發出的事件,當它發出事件時,就會跑 console.log('QQ iDontKnow')
  • 當按下 event 時,會觸發在 BigTextComponent 所監聽的事件 ('myEvent')。

如果我們不主動使用 $bus.$off('myEvent', this.eventHandler); 來移除元件的監聽,即使按下 minus 按鈕來銷毀元件,還是會殘留這些元件的監聽,造成以上提到的兩個問題:

  • 按下 event 時,會執行多於一個 console.log('QQ iDontKnow')
  • memory 裏仍然存有舊元件的那筆極長的陣列資料

因此,需要主動在元件加入移除監視事件的程式碼來解決問題。

Vue 3 的 mitt

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

總結

  • 要實現跨元件傳遞資料,在 Vue 2 可以使用 event bus,Vue 3 則使用 mitt 插件來完成。
  • 步驟就是在某一元件,使用 $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


上一篇
不只懂 Vue 語法:如何使用 v-model 實現父子元件傳遞資料?
下一篇
不只懂 Vue 語法:如何透過路由實現跨頁面傳遞資料?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言