Vue 組件通訊方式全面詳解

衆所周知,Vue 主要思想之一就是組件式開發。所以,在實際的項目開發中,確定會以組件的開發模式進行。形如頁面和頁面之間須要通訊同樣,Vue 組件和組件之間確定也須要互通有無、共享狀態。接下來,咱們就悉數給你們展現全部 Vue 組件之間的通訊方式。javascript

組件關係

Vue 組件通訊.png


上面展現的圖片能夠引入全部 Vue 組件的關係形式:

  • A 組件和 B 組件、B 組件和 C 組件、B 組件和 D 組件造成了父子關係
  • C 組件和 D 組件造成了兄弟關係
  • A 組件和 C 組件、A 組件和 D 組件造成了隔代關係(其中的層級多是多級,即隔多代)

組件通訊

這麼多的組件關係,那麼組件和組件之間又有哪些通訊的方式呢?各類方式的區別又是什麼?適用場景又是什麼呢?帶着問題繼續往下看吧!html

一、props$emit

用過 Vue 技術棧開發項目過的開發者對這樣一個組合確定不會陌生,這種組件通訊的方式是咱們運用的很是多的一種。props 以單向數據流的形式能夠很好的完成父子組件的通訊。vue

所謂單向數據流:就是數據只能經過 props 由父組件流向子組件,而子組件並不能經過修改 props 傳過來的數據修改父組件的相應狀態。至於爲何這樣作,Vue 官網作出瞭解釋:java

全部的 prop 都使得其父子 prop 之間造成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,可是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而致使你的應用的數據流向難以理解。

額外的,每次父級組件發生更新時,子組件中全部的 prop 都將會刷新爲最新的值。這意味着你不該該在一個子組件內部改變 prop。若是你這樣作了,Vue 會在瀏覽器的控制檯中發出警告。

——Vue 官網git

正由於這個特性,因而就有了對應的 $emit$emit 用來觸發當前實例上的事件。對此,咱們能夠在父組件自定義一個處理接受變化狀態的邏輯,而後在子組件中如若相關的狀態改變時,就觸發父組件的邏輯處理事件。github

// 父組件
Vue.component('parent', {
  template:` <div> <p>this is parent component!</p> <child :message="message" v-on:getChildData="getChildData"></child> </div> `,
  data() {
    return {
      message: 'hello'
    }
  },
  methods:{
    // 執行子組件觸發的事件
    getChildData(val) {
      console.log(val);
    }
  }
});

// 子組件
Vue.component('child', {
  template:` <div> <input type="text" v-model="myMessage" @input="passData(myMessage)"> </div> `,
  /** * 獲得父組件傳遞過來的數據 * 這裏的定義最好是寫成數據校驗的形式,省得獲得的數據是咱們意料以外的 * * props: { * message: { * type: String, * default: '' * } * } * */
  props:['message'], 
  data() {
    return {
      // 這裏是必要的,由於你不能直接修改 props 的值
      myMessage: this.message
    }
  },
  methods:{
    passData(val) {
      // 數據狀態變化時觸發父組件中的事件
      this.$emit('getChildData', val);
    }
  }
});
    
var app=new Vue({
  el: '#app',
  template: ` <div> <parent /> </div> `
});
複製代碼

在上面的例子中,有父組件 parent 和子組件 child。vuex

  • 1)、 父組件傳遞了 message 數據給子組件,而且經過v-on綁定了一個 getChildData 事件來監聽子組件的觸發事件;
  • 2)、 子組件經過 props 獲得相關的 message 數據,而後將數據緩存在 data 裏面,最後當屬性數據值發生變化時,經過 this.$emit 觸發了父組件註冊的 getChildData 事件處理數據邏輯。

二、$attrs$listeners

上面這種組件通訊的方式只適合直接的父子組件,也就是若是父組件A下面有子組件B,組件B下面有組件C,這時若是組件A直接想傳遞數據給組件C那就行不通了! 只能是組件A經過 props 將數據傳給組件B,而後組件B獲取到組件A 傳遞過來的數據後再經過 props 將數據傳給組件C。固然這種方式是很是複雜的,無關組件中的邏輯業務一種增多了,代碼維護也沒變得困難,再加上若是嵌套的層級越多邏輯也複雜,無關代碼越多!redux

針對這樣一個問題,Vue 2.4 提供了$attrs$listeners 來實現可以直接讓組件A傳遞消息給組件C。api

// 組件A
Vue.component('A', {
  template: ` <div> <p>this is parent component!</p> <B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B> </div> `,
  data() {
    return {
      message: 'hello',
      messagec: 'hello c' //傳遞給c組件的數據
    }
  },
  methods: {
    // 執行B子組件觸發的事件
    getChildData(val) {
      console.log(`這是來自B組件的數據:${val}`);
    },
    
    // 執行C子組件觸發的事件
    getCData(val) {
      console.log(`這是來自C組件的數據:${val}`);
    }
  }
});

// 組件B
Vue.component('B', {
  template: ` <div> <input type="text" v-model="mymessage" @input="passData(mymessage)"> <!-- C組件中能直接觸發 getCData 的緣由在於:B組件調用 C組件時,使用 v-on 綁定了 $listeners 屬性 --> <!-- 經過v-bind 綁定 $attrs 屬性,C組件能夠直接獲取到 A組件中傳遞下來的 props(除了 B組件中 props聲明的) --> <C v-bind="$attrs" v-on="$listeners"></C> </div> `,
  /** * 獲得父組件傳遞過來的數據 * 這裏的定義最好是寫成數據校驗的形式,省得獲得的數據是咱們意料以外的 * * props: { * message: { * type: String, * default: '' * } * } * */
  props: ['message'],
  data(){
    return {
      mymessage: this.message
    }
  },
  methods: {
    passData(val){
      //觸發父組件中的事件
      this.$emit('getChildData', val)
    }
  }
});

// 組件C
Vue.component('C', {
  template: ` <div> <input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"> </div> `,
  methods: {
    passCData(val) {
      // 觸發父組件A中的事件
      this.$emit('getCData',val)
    }
  }
});
    
var app=new Vue({
  el:'#app',
  template: ` <div> <A /> </div> `
});
複製代碼

在上面的例子中,咱們定義了 A,B,C 三個組件,其中組件B 是組件 A 的子組件,組件C 是組件B 的子組件。數組

  • 1). 在組件 A 裏面爲組件 B 和組件 C 分別定義了一個屬性值(message,messagec)和監聽事件(getChildData,getCData),並將這些經過 props 傳遞給了組件 A 的直接子組件 B;
  • 2). 在組件 B 中經過 props 只獲取了與自身直接相關的屬性(message),並將屬性值緩存在了 data 中,以便後續的變化監聽處理,而後當屬性值變化時觸發父組件 A 定義的數據邏輯處理事件(getChildData)。關於組件 B 的直接子組件 C,傳遞了屬性 $attrs 和綁定了事件 $listeners
  • 3). 在組件 C 中直接在 v-model 上綁定了 $attrs 屬性,經過 v-on 綁定了 $listeners

最後就將 $attrs$listeners 單獨拿出來講說吧!

  • $attrs:包含了父做用域中不被 prop 所識別 (且獲取) 的特性綁定 (classstyle 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定屬性 (class和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件。

  • $listeners:包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件。

三、中央事件總線 EventBus

對於父子組件之間的通訊,上面的兩種方式是徹底能夠實現的,可是對於兩個組件不是父子關係,那麼又該如何實現通訊呢?在項目規模不大的狀況下,徹底可使用中央事件總線 EventBus 的方式。若是你的項目規模是大中型的,那你可使用咱們後面即將介紹的 Vuex 狀態管理

EventBus 經過新建一個 Vue 事件 bus 對象,而後經過 bus.$emit 觸發事件,bus.$on 監聽觸發的事件。

// 組件 A
Vue.component('A', {
  template: ` <div> <p>this is A component!</p> <input type="text" v-model="mymessage" @input="passData(mymessage)"> </div> `,
  data() {
    return {
      mymessage: 'hello brother1'
    }
  },
  methods: {
    passData(val) {
      //觸發全局事件globalEvent
      this.$EventBus.$emit('globalEvent', val)
    }
  }
});

// 組件 B
Vue.component('B', {
  template:` <div> <p>this is B component!</p> <p>組件A 傳遞過來的數據:{{brothermessage}}</p> </div> `,
  data() {
    return {
      mymessage: 'hello brother2',
      brothermessage: ''
    }
  },
  mounted() {
    //綁定全局事件globalEvent
    this.$EventBus.$on('globalEvent', (val) => {
      this.brothermessage = val;
    });
  }
});

//定義中央事件總線
const EventBus = new Vue();

// 將中央事件總線賦值到 Vue.prototype 上,這樣全部組件都能訪問到了
Vue.prototype.$EventBus = EventBus;

const app = new Vue({
  el: '#app',
  template: ` <div> <A /> <B /> </div> `
});
複製代碼

在上面的實例中,咱們定義了組件 A 和組件 B,可是組件 A 和組件 B 之間沒有任何的關係。

  • 1)、 首先咱們經過 new Vue() 實例化了一個 Vue 的實例,也就是咱們這裏稱呼的中央事件總線 EventBus ,而後將其賦值給了 Vue.prototype.$EventBus,使得全部的業務邏輯組件都可以訪問到;
  • 2)、 而後定義了組件 A,在組件 A 裏面定義了一個處理的方法 passData,主要定義觸發一個全局的 globalEvent 事件,並傳遞一個參數;
  • 3). 最後定義了組件 B,在組件 B 裏面的 mounted 生命週期監聽了組件 A 裏面定義的全局 globalEvent 事件,並在回調函數裏面執行了一些邏輯處理。

中央事件總線 EventBus 很是簡單,就是任意組件和組件之間打交道,沒有多餘的業務邏輯,只須要在狀態變化組件觸發一個事件,而後在處理邏輯組件監聽該事件就能夠。該方法很是適合小型的項目!

四、provideinject

熟悉 React 開發的同窗對 Context API 確定不會陌生吧!在 Vue 中也提供了相似的 API 用於組件之間的通訊。

在父組件中經過 provider 來提供屬性,而後在子組件中經過 inject 來注入變量。不論子組件有多深,只要調用了 inject 那麼就能夠注入在 provider 中提供的數據,而不是侷限於只能從當前父組件的 prop 屬性來獲取數據,只要在父組件的生命週期內,子組件均可以調用。這和 React 中的 Context API 有沒有很類似!

// 定義 parent 組件
Vue.component('parent', {
  template: ` <div> <p>this is parent component!</p> <child></child> </div> `,
  provide: {
    for:'test'
  },
  data() {
    return {
      message: 'hello'
    }
  }
});

// 定義 child 組件
Vue.component('child', {
  template: ` <div> <input type="tet" v-model="mymessage"> </div> `,
  inject: ['for'],	// 獲得父組件傳遞過來的數據
  data(){
    return {
      mymessage: this.for
    }
  },
});

const app = new Vue({
  el: '#app',
  template: ` <div> <parent /> </div> `
});
複製代碼

在上面的實例中,咱們定義了組件 parent 和組件 child,組件 parent 和組件 child 是父子關係。

  • 1)、 在 parent 組件中,經過 provide 屬性,以對象的形式向子孫組件暴露了一些屬性
  • 2)、 在 child 組件中,經過 inject 屬性注入了 parent 組件提供的數據,實際這些經過 inject 注入的屬性是掛載到 Vue 實例上的,因此在組件內部能夠經過 this 來訪問。

⚠️ 注意:官網文檔說起 provide 和 inject 主要爲高階插件/組件庫提供用例,並不推薦直接用於應用程序代碼中。

關於 provideinject 這對屬性的更多具體用法能夠參照官網的文檔


寫到這裏有點累了,前面大體介紹了四種 Vue 組件通訊的方式,你以爲這些就夠了嗎?不不不,講完前面四種方式後面還有四種等着咱們呢! 藉此加個分割線,壓壓驚!😭對了,千萬不要說學不動了,只要還有一口氣,都要繼續學!


五、v-model

這種方式和前面講到的 props 有點類型,可是既然單獨提出來講了,那確定也有其獨特之處!無論了,先上代碼吧!

// 定義 parent 組件
Vue.component('parent', {
  template: ` <div> <p>this is parent component!</p> <p>{{message}}</p> <child v-model="message"></child> </div> `,
  data() {
    return {
      message: 'hello'
    }
  }
});

// 定義 child 組件
Vue.component('child', {
  template: ` <div> <input type="text" v-model="mymessage" @change="changeValue"> </div> `,
  props: {
    value: String, // v-model 會自動傳遞一個字段爲 value 的 props 屬性
  },
  data() {
    return {
      mymessage: this.value
    }
  },
  methods: {
    changeValue() {
      this.$emit('input', this.mymessage); //經過如此調用能夠改變父組件上 v-model 綁定的值
    }
  },
});

const app = new Vue({
  el: '#app',
  template: ` <div> <parent /> </div> `
});
複製代碼

說到 v-model 這個指定,你們確定會想到雙向數據綁定,如 input 輸入值,下面的顯示就是實時的根據輸入的不一樣而顯示相應的內容。剛開始學習 Vue 的時候有沒有以爲很神奇,無論你有沒有,反正我有過這種感受!

關於詳細的 v-model 用法和自定義組件 v-model 的實現,能夠到這裏查看!這裏咱們主要講解 v-model 是如何實現父子組件通訊的。

在上面的實例代碼中,咱們定義了 parent 和 child 兩個組件,這兩個組件是父子關係,v-model 也只能實現父子組件之間的通訊。

  • 1)、 在 parent 組件中,咱們給自定義的 child 組件實現了 v-model 綁定了 message 屬性。此時至關於給 child 組件傳遞了 value 屬性和綁定了 input 事件。
  • 2)、 瓜熟蒂落,在定義的 child 組件中,能夠經過 props 獲取 value 屬性,根據 props 單向數據流的原則,又將 value 緩存在了 data 裏面的 mymessage 上,再在 input 上經過 v-model 綁定了 mymessage 屬性和一個 change 事件。當 input 值變化時,就會觸發 change 事件,處理 parent 組件經過 v-model 給 child 組件綁定的 input 事件,觸發 parent 組件中 message 屬性值的變化,完成 child 子組件改變 parent 組件的屬性值。

這裏主要是 v-model 的實現原理要着重瞭解一下!這種方式的用處適合於將展現組件和業務邏輯組件分離。

六、$parent$children

這裏要說的這種方式就比較直觀了,直接操做父子組件的實例。$parent 就是父組件的實例對象,而 $children 就是當前實例的直接子組件實例了,不過這個屬性值是數組類型的,且並不保證順序,也不是響應式的。

// 定義 parent 組件
Vue.component('parent', {
  template: ` <div> <p>this is parent component!</p> <button @click="changeChildValue">test</button> <child /> </div> `,
  data() {
    return {
      message: 'hello'
    }
  },
  methods: {
    changeChildValue(){
      this.$children[0].mymessage = 'hello';
    }
  },
});

// 定義 child 組件
Vue.component('child', {
  template:` <div> <input type="text" v-model="mymessage" @change="changeValue" /> </div> `,
  data() {
    return {
      mymessage: this.$parent.message
    }
  },
  methods: {
    changeValue(){
      this.$parent.message = this.mymessage;//經過如此調用能夠改變父組件的值
    }
  },
});
    
const app = new Vue({
  el: '#app',
  template: ` <div> <parent /> </div> `
});
複製代碼

在上面實例代碼中,分別定義了 parent 和 child 組件,這兩個組件是直接的父子關係。兩個組件分別在內部定義了本身的屬性。在 parent 組件中,直接經過 this.$children[0].mymessage = 'hello';child 組件內的 mymessage 屬性賦值,而在 child 子組件中,一樣也是直接經過this.$parent.messageparent 組件中的 message 賦值,造成了父子組件通訊。

關於 $parent$children 這對屬性的詳細介紹能夠查詢官網文檔!

七、$boradcast$dispatch

這也是一對成對出現的方法,不過只是在 Vue1.0 中提供了,而 Vue2.0 被廢棄了,可是仍是有不少開源軟件都本身封裝了這種組件通訊的方式,好比 Mint UIElement UIiView 等。

// broadcast 方法的主邏輯處理方法
function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    const name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat(params));
    }
  });
}

export default {
  methods: {
    // 定義 dispatch 方法
    dispatch(componentName, eventName, params) {
      let parent = this.$parent;
      let name = parent.$options.componentName;
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    
    // 定義 broadcast 方法
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};
複製代碼

上面所示的代碼,通常都做爲一個 mixins 去混入使用, broadcast 是向特定的父組件觸發事件,dispatch 是向特定的子組件觸發事件,本質上這種方式仍是 onemit 的封裝,在一些基礎組件中都很實用。

由於在 Vue 2.0 這個 API 已經廢棄,那咱們在這裏也就提一下,若是想詳細瞭解 Vue 1.0 和其餘基於 Vue 的 UI 框架關於這個 API 的實現,能夠點擊查看這篇文章

八、Vuex 狀態管理

Vuex 是狀態管理工具,實現了項目狀態的集中式管理。工具的實現借鑑了 FluxRedux、和 The Elm Architecture 的模式和概念。固然與其餘模式不一樣的是,Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。詳細的關於 Vuex 的介紹,你既能夠去查看官網文檔,也能夠查看本專欄關於 Vuex 一系列的介紹。

總結

寫到這裏,Vue 中關於組件通訊的全部方式就介紹完了,是否是感受仍是頗豐的呢?其實還有另外的兩種方式能夠實現組件的通訊,一是經過 Vue Router 通訊,二是經過瀏覽器本地存儲實現組件通訊。關於這兩種方式,這裏我就不講了,固然我會在本專欄中單獨開篇講解的,但願你們有興趣就去看看!

準確來講本文詳細講解了實現 Vue 通訊的六種方式,每種方式都有其特色。在實際的項目,你們能夠酌情的進行使用。

相關文章
相關標籤/搜索