Vue組件通訊方式全面詳解
- 2020 年 4 月 2 日
- 筆記
vue組件通訊方式全面詳解
眾所周知,Vue主要思想就是組件化開發。因為,在實際的項目開發中,肯定會以組件的開發模式進行。形如頁面和頁面之間需要通訊一樣,Vue 組件和組件之間肯定也需要互通有無、共享狀態。接下來,我們就悉數給大家展示所有 Vue 組件之間的通訊方式。
組件關係
- App組件和A組件、A組件和B組件、B組件和C組件形成父子關係
- B組件和D組件形成兄弟關係
- App組件和C組件、App和B組件形成了隔代關係(其中的層級可能是多級,既隔多代)
組件通訊
這麼多的組件關係,那麼組件和組件之間又有哪些通訊的方式呢?各種方式的區別又是什麼?適用場景又是什麼呢?
props和$emit
這種方式是我們日常開發中應用最多的一種方式。
props以單向數據流的形式可以很好的完成父子組件的通訊
所謂單向數據流:就是數據只能通過 props 由父組件流向子組件,而子組件並不能通過修改 props 傳過來的數據修改父組件的相應狀態。至於為什麼這樣做,Vue 官網做出了解釋:
**所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。
額外的,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新為最新的值。這意味著你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制台中發出警告。
正因為這個特性,於是就有了對應的 $emit
。$emit
用來觸發當前實例上的事件。對此,我們可以在父組件自定義一個處理接受變化狀態的邏輯,然後在子組件中如若相關的狀態改變時,就觸發父組件的邏輯處理事件。
let Child = { template: `<div> <input type="text" v-model='msg'/> <button @click='handleClick'>傳遞</button> </div>`, props: ['msg'], methods: { handleClick() { this.$emit('getChildData', '子組件數據') } }, } let Parent = { data() { return { msg: '小馬哥', val:'' } }, methods: { getChildData(val) { this.val = val; } }, template: ` <div> <p>我是一個父組件</p> <p>我是{{val}}</p> <Child :msg='msg' @getChildData='getChildData'></Child> </div> `, components: { Child } } let vm = new Vue({ el: '#app', template: ` <div> <Parent></Parent> </div> `, components: { Parent } })
- 父傳子:父組件傳遞msg數據給子組件,通過v-bind綁定msg,子組件中直接可以用props接收綁定的數據
- 子傳父:子組件觸發相應的事件,通過$emit觸發事件傳遞數據,父組件中綁定對應的事件,通過$on監聽對應的事件 接收子組件傳遞的數據
EventBus-中央事件匯流排
如果想實現兄弟組件之間進行通訊,在項目規模不大的情況下,完全可以使用中央事件匯流排EventBus
的方式。如果你的項目規模是大中型的,那我們會使用vuex狀態管理
EventBus
通過新建一個Vue
事件bus
對象,通過bus.$emit
觸發事件,bus.$on監聽觸發的事件。
Vue.component('A', { template: ` <div> <p>我是A組件</p> <button @click='handleClick'>A傳遞到B</button> </div> `, data() { return { msg: 'hello 小馬哥' } }, methods: { handleClick() { this.$bus.$emit('globalEvent',this.msg); } }, }) Vue.component('B', { template: ` <div> <p>我是B組件</p> <h3>{{aValue}}</h3> </div> `, data() { return { aValue: '' } }, created () { this.$bus.$on('globalEvent',(val)=>{ this.aValue = val; }) }, }) // 定義中央事件匯流排 let bus = new Vue(); // 將中央事件匯流排賦值給Vue.prototype中,這樣所有組件都能訪問到了 Vue.prototype.$bus = bus; let vm = new Vue({ el: '#app', template: ` <div> <A></A> <B></B> </div> `, })
$attrs和$listeners
通過 props
進行組件通訊的方式只適合直接的父子組件,如果父組件A下面有子組件B,組件B下面有組件C,這時如果組件A直接想傳遞數據給組件C那就行不通了! 只能是組件A通過 props 將數據傳給組件B,然後組件B獲取到組件A 傳遞過來的數據後再通過 props 將數據傳給組件C。當然這種方式是非常複雜的,無關組件中的邏輯業務一種增多了,程式碼維護也沒變得困難,再加上如果嵌套的層級越多邏輯也複雜,無關程式碼越多!
針對這樣一個問題,Vue 2.4提供了$attrs
和$listeners
來實現能夠直接讓組件A傳遞消息給組件C
Vue.component('A', { template: ` <div> <p>我是A組件</p> <B :msg='msg' @getCData='getCData'></B> </div> `, methods: { getCData(val) { alert(val) } }, data() { return { msg: 'hello 小馬哥' } }, }) Vue.component('B', { template: ` <div> <p>我是B組件</p> <!-- 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: ['msg'], data() { return { } } }) Vue.component('C', { template: ` <div> <p>我是C組件</p> <p>{{$attrs.msg}}</p> <button @click='handleClick'>傳遞數據</button> </div> `, methods: { handleClick() { this.$emit('getCData', 'C組件的數據') } }, data() { return { } } }) let vm = new Vue({ el: '#app', template: ` <div> <A></A> </div> `, })
$attrs
:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class
和style
除外)。當一個組件沒有聲明任何 prop 時,這裡會包含所有父作用域的綁定屬性 (class和style
除外),並且可以通過v-bind="$attrs"
傳入內部組件。$listeners
:包含了父作用域中的 (不含.native
修飾器的)v-on
事件監聽器。它可以通過v-on="$listeners"
傳入內部組件。
provide和inject
在父組件中通過 provider
來提供屬性,然後在子組件中通過 inject 來注入變數。不論子組件有多深,只要調用了 inject
那麼就可以注入在 provider 中提供的數據,而不是局限於只能從當前父組件的 prop 屬性來獲取數據,只要在父組件的生命周期內,子組件都可以調用。這和 React 中的 Context API
有沒有很相似!
Vue.component('A', { template: ` <div> <p>我是A組件</p> <B></B> </div> `, provide:{ a:"祖先A的數據" }, data() { return { msg: 'hello 小馬哥' } }, }) Vue.component('B', { template: ` <div> <p>我是B組件</p> <C></C> </div> `, data() { return { } } }) Vue.component('C', { template: ` <div> <p>我是C組件</p> <h3>{{a}}</h3> </div> `, inject:['a'], data() { return { } } }) let vm = new Vue({ el: '#app', template: ` <div> <A></A> </div> `, })
- 在
parent
組件中,通過provide
屬性,以對象的形式向子孫組件暴露了一些屬性 - 在
child
組件中,通過inject
屬性注入了parent
組件提供的數據,實際這些通過inject
注入的屬性是掛載到 Vue 實例上的,所以在組件內部可以通過 this 來訪問
⚠️ 注意:官網文檔提及 provide 和 inject 主要為高階插件/組件庫提供用例,並不推薦直接用於應用程式程式碼中。
$parent和$children
這裡要說的這種方式就比較直觀了,直接操作父子組件的實例。$parent
就是父組件的實例對象,而 $children
就是當前實例的直接子組件實例了,不過這個屬性值是數組類型的,且並不保證順序,也不是響應式的。
Vue.component('Parent', { template: ` <div> <p>我是父組件</p> {{msg}} <hr/> <Child></Child> </div> `, mounted () { //讀取子組件數據,注意$children並不保證順序,也不是響應式的 console.log(this.$children[0].a) }, data() { return { msg: 'hello 小馬哥' } }, }) Vue.component('Child', { template: ` <div> <p>我是孩子組件</p> <input type="text" @input='changeValue'/> <h2>{{myMsg}}</h2> </div> `, methods: { changeValue() { this.$parent.msg = '子組件中的數據' } }, data() { return { myMsg:this.$parent.msg, a:"小馬哥" } } }) let vm = new Vue({ el: '#app', template: ` <div> <Parent></Parent> </div> `, })
Vuex狀態管理
Vuex 是狀態管理工具,實現了項目狀態的集中式管理。工具的實現借鑒了 Flux、Redux、和 The Elm Architecture 的模式和概念。當然與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。詳細的關於 Vuex 的介紹,你既可以去查看官網文檔,也可以查看本專欄關於 Vuex 一系列的介紹。