衆所周知,Vue 主要思想之一就是組件式開發。所以,在實際的項目開發中,確定會以組件的開發模式進行。形如頁面和頁面之間須要通訊同樣,Vue 組件和組件之間確定也須要互通有無、共享狀態。接下來,咱們就悉數給你們展現全部 Vue 組件之間的通訊方式。javascript
這麼多的組件關係,那麼組件和組件之間又有哪些通訊的方式呢?各類方式的區別又是什麼?適用場景又是什麼呢?帶着問題繼續往下看吧!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
$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 的子組件。數組
$attrs
和綁定了事件 $listeners
;$attrs
屬性,經過 v-on 綁定了 $listeners
;最後就將 $attrs
和 $listeners
單獨拿出來講說吧!
$attrs
:包含了父做用域中不被 prop 所識別 (且獲取) 的特性綁定 (class
和 style
除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定屬性 (class和 style
除外),而且能夠經過 v-bind="$attrs"
傳入內部組件。
$listeners
:包含了父做用域中的 (不含 .native
修飾器的) v-on
事件監聽器。它能夠經過 v-on="$listeners"
傳入內部組件。
對於父子組件之間的通訊,上面的兩種方式是徹底能夠實現的,可是對於兩個組件不是父子關係,那麼又該如何實現通訊呢?在項目規模不大的狀況下,徹底可使用中央事件總線 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 之間沒有任何的關係。
new Vue()
實例化了一個 Vue 的實例,也就是咱們這裏稱呼的中央事件總線 EventBus ,而後將其賦值給了 Vue.prototype.$EventBus
,使得全部的業務邏輯組件都可以訪問到;globalEvent
事件,並傳遞一個參數;mounted
生命週期監聽了組件 A 裏面定義的全局 globalEvent
事件,並在回調函數裏面執行了一些邏輯處理。中央事件總線 EventBus
很是簡單,就是任意組件和組件之間打交道,沒有多餘的業務邏輯,只須要在狀態變化組件觸發一個事件,而後在處理邏輯組件監聽該事件就能夠。該方法很是適合小型的項目!
provide
和 inject
熟悉 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
是父子關係。
parent
組件中,經過 provide
屬性,以對象的形式向子孫組件暴露了一些屬性child
組件中,經過 inject
屬性注入了 parent
組件提供的數據,實際這些經過 inject
注入的屬性是掛載到 Vue 實例上的,因此在組件內部能夠經過 this 來訪問。⚠️ 注意:官網文檔說起 provide 和 inject 主要爲高階插件/組件庫提供用例,並不推薦直接用於應用程序代碼中。
關於 provide
和 inject
這對屬性的更多具體用法能夠參照官網的文檔。
寫到這裏有點累了,前面大體介紹了四種 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 也只能實現父子組件之間的通訊。
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.message
給 parent
組件中的 message
賦值,造成了父子組件通訊。
關於 $parent
和 $children
這對屬性的詳細介紹能夠查詢官網文檔!
$boradcast
和 $dispatch
這也是一對成對出現的方法,不過只是在 Vue1.0
中提供了,而 Vue2.0
被廢棄了,可是仍是有不少開源軟件都本身封裝了這種組件通訊的方式,好比 Mint UI、Element UI 和 iView 等。
// 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
是向特定的子組件觸發事件,本質上這種方式仍是 on
和 emit
的封裝,在一些基礎組件中都很實用。
由於在 Vue 2.0
這個 API 已經廢棄,那咱們在這裏也就提一下,若是想詳細瞭解 Vue 1.0
和其餘基於 Vue 的 UI 框架關於這個 API 的實現,能夠點擊查看這篇文章!
Vuex 是狀態管理工具,實現了項目狀態的集中式管理。工具的實現借鑑了 Flux、Redux、和 The Elm Architecture 的模式和概念。固然與其餘模式不一樣的是,Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。詳細的關於 Vuex 的介紹,你既能夠去查看官網文檔,也能夠查看本專欄關於 Vuex 一系列的介紹。
寫到這裏,Vue 中關於組件通訊的全部方式就介紹完了,是否是感受仍是頗豐的呢?其實還有另外的兩種方式能夠實現組件的通訊,一是經過 Vue Router 通訊,二是經過瀏覽器本地存儲實現組件通訊。關於這兩種方式,這裏我就不講了,固然我會在本專欄中單獨開篇講解的,但願你們有興趣就去看看!
準確來講本文詳細講解了實現 Vue 通訊的六種方式,每種方式都有其特色。在實際的項目,你們能夠酌情的進行使用。