第12屆鐵人賽Day 12 跨多層的Vue元件資料傳遞: event bus

這週都在討論props(從外層傳到內層元件)與emit(從內層傳到外層), 如果是平行關係的元件(而非父子關係),要如何進行資料傳遞呢?

這時候就會出動一台公車,叫做event bus啦!

首先請熟記:
$bus.$emit: 發送事件
$bus.$on: 接收事件

在今天的例子裡,從Vue Devtools看到元件的階層如下:

<Root>
  <Sender>
  <Receiver>
  <Foot>

我們來引用經典的日本電影《情書》(1995),模仿一個Sender元件傳送資料給Receiver元件。

實作需求:

Sender $bus.$emit: 發送事件

this.$bus.$emit發送loveLetter事件,透過JavaScript的setTimeOut非同步callback,3秒鐘後收到alert

Receiver $bus.$on: 接收事件

alert按下確定後,Receiver可以收到eventBus傳來新的message

步驟:

Step1. Vue.js進入點:home.js設定全域的event bus

let eventBus = new Vue({});定義一個空的Vue instance

利用Object.defineProperty() 實現雙向繫結,以觀察observe屬性的值的變動

Object.defineProperty() 會帶3個引數:

1.目標物件 Vue.prototype
2.需要定義的屬性或方法的名字,在這裡是 $bus
3.目標屬性所擁有的特性: 在這裡是 get()取值

import TurbolinksAdapter from 'vue-turbolinks'
import Vue from 'vue/dist/vue.esm'
import Foot from "../components/foot"
import Sender from "../components/sender"
import Receiver from "../components/receiver"

Vue.use(TurbolinksAdapter)

document.addEventListener('turbolinks:load', () => {
  let el = document.querySelector("#content");

  if (el){

    Object.defineProperty(Vue.prototype, '$bus', {
      get() {
        return this.$root.bus;
      }
    });
    // 名為eventBus的vue instance
    let eventBus = new Vue({});
    new Vue({
      el,
      data: {
        day: "第 12 天",
        topic: "跨多層的Vue元件資料傳遞: event bus",
        bus: eventBus
      },
      components: { Sender, Receiver, Foot  }
    })    
  }
})

Step2. Sender元件

在created()這個hook上
this.$bus.$emit發送loveLetter事件,透過JavaScript的setTimeOut非同步callback,讓3秒鐘後Sender元件的message被更換為電影情書的經典台詞。

<template>
  <div class="sender rounded-lg"></div>
</template>

<script>
  export default {              
    data: function () {
      return {
        msg: '寄給你一封情書'
      }
    },
    created() {
      // async
      setTimeout(() => {
        // 發送事件
        this.$bus.$emit('loveLetter', {
          msg: 'Ting is reading: Love Letter/ラブレター',
          alert: '請收下'
        });

        this.msg = '九月的陽光下,我正偷看你的側臉。你的身影猶如蕩漾在春風中的一首歌,你一定全都知道,你一定全都不在乎,就這樣回過頭,清涼的一笑。'
      }, 3000);
    }
  }
</script>

<style scoped>
<!-- 略 -->
</style>

Step3. Receiver元件

3秒鐘後this.$bus.$on會接收事件(可想成是搭上oneventBus這班名為loveLetter的事件公車):

  • 透過event更換msg。
  • event裡的alert的值請收下透過彈出對話框顯示。
<template>
  <div class="receiver rounded-lg"><i class="far fa-heart mr-2"></i></div>
</template>

<script>
  export default {              
    name: '',
    data: function () {
      return {
        msg: '收件人:Ting'
      }
    },
    created() {
    // 註冊監聽事件
    this.$bus.$on('loveLetter', (event) => {
      this.msg = event.msg;
      alert(event.alert);
      console.log(event);
      // {msg: "Ting is reading: Love Letter/ラブレター", alert: "請收下"}
    });
  },
  }
</script>

<style scoped>
<!-- 略 -->
</style>

完成圖:

全域event bus雖然方便,但是不容易偵錯及維護,

所以請注意:

  1. 盡量在同階層的或是跨越多階層的元件溝通再使用。 父子元件溝通請使用前幾天聊的props/emit
  2. 大型的專案就要使用Vuex來管理狀態囉,

明天來介紹Vuex吧~之後的Rails專案應該會用到!

後記:event bus是我一直放在心上,但是之前還沒有花時間去弄懂的例子,終於趁著鐵人賽的絕佳機會,總算實作出一個小東西了~

Ref: