如何實現Vue已經棄用的$dispatch和$broadcast方法?


對於父子(含跨級)傳遞數據的通信方式,Vue.js 並沒有提供原生的 API 來支持,而是推薦使用大型數據狀態管理工具 Vuex,但 Vuex 對於小型項目來說用起來真的很麻煩。

在 Vue.js 1.x 中,提供了兩個方法:$dispatch$broadcast ,前者用於向上級派發事件,只要是它的父級(一級或多級以上),都可以在組件內通過 $on (或 events,2.x 已廢棄)監聽到,后者相反,是由上級向下級廣播事件的。

這兩種方法一旦發出事件后,任何組件都是可以接收到的,就近原則,而且會在第一次接收到后停止冒泡,除非返回 true。

下面我們來自行實現 dispatch 和 broadcast 方法,目標是解決父子組件(含跨級)間的通信問題。

我們要實現的 dispatch 和 broadcast 方法,將具有以下功能:

  • 在子組件調用 dispatch 方法,向上級指定的組件實例(最近的)上觸發自定義事件,並傳遞數據,且該上級組件已預先通過 $on 監聽了這個事件;
  • 相反,在父組件調用 broadcast 方法,向下級指定的組件實例(最近的)上觸發自定義事件,並傳遞數據,且該下級組件已預先通過 $on 監聽了這個事件。

實現這對方法的關鍵點在於,如何正確地向上或向下找到對應的組件實例,並在它上面觸發方法。在設計一個新功能(features)時,可以先確定這個功能的 API 是什么,也就是說方法名、參數、使用樣例,確定好 API,再來寫具體的代碼。

因為 Vue.js 內置的方法,才是以 $ 開頭的,比如 $nextTick$emit 等,為了避免不必要的沖突並遵循規范,這里的 dispatch 和 broadcast 方法名前不加 $。並且該方法可能在很多組件中都會使用,復用起見,我們封裝在混合(mixins)里。那它的使用樣例可能是這樣的:

// 部分代碼省略
import Emitter from '../mixins/emitter.js'

export default {
  mixins: [ Emitter ],
  methods: {
    handleDispatch () {
      this.dispatch();  // ①
    },
    handleBroadcast () {
      this.broadcast();  // ②
    }
  }
}

上例中行 ① 和行 ② 的兩個方法就是在導入的混合 emitter.js 中定義的,這個稍后我們再講,先來分析這兩個方法應該傳入什么參數。一般來說,為了跟 Vue.js 1.x 的方法一致,第一個參數應當是自定義事件名,比如 “test”,第二個參數是傳遞的數據,比如 “Hello, Vue.js”,但在這里,有什么問題呢?只通過這兩個參數,我們沒辦法知道要在哪個組件上觸發事件,因為自行實現的這對方法,與 Vue.js 1.x 的原生方法機理上是有區別的。上文說到,實現這對方法的關鍵點在於准確地找到組件實例。那在尋找組件實例上,就是通過遍歷來匹配組件的 name 選項,在獨立組件(庫)里,每個組件的 name 值應當是唯一的。

先來看下 emitter.js 的代碼:

function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    const name = child.$options.name;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.name;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

因為是用作 mixins 導入,所以在 methods 里定義的 dispatch 和 broadcast 方法會被混合到組件里,自然就可以用 this.dispatchthis.broadcast 來使用。

這兩個方法都接收了三個參數,第一個是組件的 name 值,用於向上或向下遞歸遍歷來尋找對應的組件,第二個和第三個就是上文分析的自定義事件名稱和要傳遞的數據。

可以看到,在 dispatch 里,通過 while 語句,不斷向上遍歷更新當前組件(即上下文為當前調用該方法的組件)的父組件實例(變量 parent 即為父組件實例),直到匹配到定義的 componentName 與某個上級組件的 name 選項一致時,結束循環,並在找到的組件實例上,調用 $emit 方法來觸發自定義事件 eventName。broadcast 方法與之類似,只不過是向下遍歷尋找。

來看一下具體的使用方法。有 A.vueB.vue 兩個組件,其中 B 是 A 的子組件,中間可能跨多級,在 A 中向 B 通信:

<!-- A.vue -->
<template>
	<button @click="handleClick">觸發事件</button>
</template>
<script>
  import Emitter from '../mixins/emitter.js';
  
  export default {
    name: 'componentA',
    mixins: [ Emitter ],
    methods: {
      handleClick () {
        this.broadcast('componentB', 'on-message', 'Hello Vue.js');
      }
    }
  }
</script>

// B.vue
export default {
  name: 'componentB',
  created () {
    this.$on('on-message', this.showMessage);
  },
  methods: {
    showMessage (text) {
      window.alert(text);
    }
  }
}

同理,如果是 B 向 A 通信,在 B 中調用 dispatch 方法,在 A 中使用 $on 監聽事件即可。

以上就是自行實現的 dispatch 和 broadcast 方法,相比 Vue.js 1.x,有以下不同:

  • 需要額外傳入組件的 name 作為第一個參數;
  • 無冒泡機制;
  • 第三個參數傳遞的數據,只能是一個(較多時可以傳入一個對象),而 Vue.js 1.x 可以傳入多個參數,當然,你對 emitter.js 稍作修改,也能支持傳入多個參數,只是一般場景傳入一個對象足以。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM