深入瞭解Vue組件七種通信方式
vue組件通信的方式,這是在面試中一個非常高頻的問題,我剛開始找實習便經常遇到這個問題,當時隻知道回到props和 $emit,後來隨著學習的深入,才發現vue組件的通信方式竟然有這麼多!
今天對vue組件通信方式進行一下總結,如寫的有疏漏之處還請大傢留言。
1. props/$emit
簡介
props和 $emit相信大傢十分的熟悉瞭,這是我們最常用的vue通信方式。
props:props可以是數組或對象,用於接收來自父組件通過v-bind傳遞的數據。當props為數組時,直接接收父組件傳遞的屬性;當 props 為對象時,可以通過type、default、required、validator等配置來設置屬性的類型、默認值、是否必傳和校驗規則。
$emit:在父子組件通信時,我們通常會使用 $emit來觸發父組件v-on在子組件上綁定相應事件的監聽。
代碼實例
下面通過代碼來實現一下props和 $emit的父子組件通信,在這個實例中,我們都實現瞭以下的通信:
父向子傳值:父組件通過 :messageFromParent=”message” 將父組件 message 值傳遞給子組件,當父組件的 input 標簽輸入時,子組件p標簽中的內容就會相應改變。
子向父傳值:父組件通過 @on-receive=”receive” 在子組件上綁定瞭 receive 事件的監聽,子組件 input 標簽輸入時,會觸發 receive 回調函數, 通過 this.$emit(‘on-receive’, this.message) 將子組件 message 的值賦值給父組件 messageFromChild ,改變父組件p標簽的內容。
請看代碼:
// 子組件代碼 <template> <div class="child"> <h4>this is child component</h4> <input type="text" v-model="message" @keyup="send" /> <p>收到來自父組件的消息:{{ messageFromParent }}</p> </div> </template> <script> export default { name: 'Child', props: ['messageFromParent'], // 通過props接收父組件傳過來的消息 data() { return { message: '', } }, methods: { send() { this.$emit('on-receive', this.message) // 通過 $emit觸發on-receive事件,調用父組件中receive回調,並將this.message作為參數 }, }, } </script>
// 父組件代碼 <template> <div class="parent"> <h3>this is parent component</h3> <input type="text" v-model="message" /> <p>收到來自子組件的消息:{{ messageFromChild }}</p> <Child :messageFromParent="message" @on-receive="receive" /> </div> </template> <script> import Child from './child' export default { name: 'Parent', data() { return { message: '', // 傳遞給子組件的消息 messageFromChild: '', } }, components: { Child, }, methods: { receive(msg) { // 接受子組件的信息,並將其賦值給messageFromChild this.messageFromChild = msg }, }, } </script>
效果預覽
2. v-slot
簡介
v-slot是 Vue2.6 版本中新增的用於統一實現插槽和具名插槽的api,用於替代 slot(2.6.0廢棄) 、 slot-scope(2.6.0廢棄) 、 scope(2.5.0廢棄) 等api。
v-slot在 template 標簽中用於提供具名插槽或需要接收 prop 的插槽,如果不指定 v-slot ,則取默認值 default 。
代碼實例
下面請看v-slot的代碼實例,在這個實例中我們實現瞭:
父向子傳值:父組件通過 <template v-slot:child>{{ message }}</template> 將父組件的message值傳遞給子組件,子組件通過 <slot name=”child”></slot> 接收到相應內容,實現瞭父向子傳值。
// 子組件代碼 <template> <div class="child"> <h4>this is child component</h4> <p>收到來自父組件的消息: <slot name="child"></slot> <!--展示父組件通過插槽傳遞的{{message}}--> </p> </div> </template>
<template> <div class="parent"> <h3>this is parent component</h3> <input type="text" v-model="message" /> <Child> <template v-slot:child> {{ message }} <!--插槽要展示的內容--> </template> </Child> </div> </template> <script> import Child from './child' export default { name: 'Parent', data() { return { message: '', } }, components: { Child, }, } </script>
效果預覽
3. $refs/ $parent/ $children/$root
簡介
我們也同樣可以通過 $refs/$parent/$children/$root 等方式獲取 Vue 組件實例,得到實例上綁定的屬性及方法等,來實現組件之間的通信。
$refs:我們通常會將 $refs綁定在DOM元素上,來獲取DOM元素的 attributes。在實現組件通信上,我們也可以將 $refs 綁定在子組件上,從而獲取子組件實例。
$parent:我們可以在 Vue 中直接通過 this.$parent 來獲取當前組件的父組件實例(如果有的話)。
$children:同理,我們也可以在 Vue 中直接通過 this.$children 來獲取當前組件的子組件實例的數組。但是需要註意的是, this.$children 數組中的元素下標並不一定對用父組件引用的子組件的順序,例如有異步加載的子組件,可能影響其在 children 數組中的順序。所以使用時需要根據一定的條件例如子組件的name去找到相應的子組件。
$root:獲取當前組件樹的根 Vue 實例。如果當前實例沒有父實例,此實例將會是其自己。通過 $root ,我們可以實現組件之間的跨級通信。
代碼實例
下面來看一個 $ parent 和 $ children 使用的實例(由於這幾個api的使用方式大同小異,所以關於 $ refs 和 $ root 的使用就不在這裡展開瞭,在這個實例中實現瞭:
父向子傳值:子組件通過 $parent.message 獲取到父組件中message的值。
子向父傳值:父組件通過 $children 獲取子組件實例的數組,在通過對數組進行遍歷,通過實例的 name 獲取到對應 Child1 子組件實例將其賦值給 child1,然後通過 child1.message 獲取到 Child1 子組件的message。
代碼如下:
// 子組件 <template> <div class="child"> <h4>this is child component</h4> <input type="text" v-model="message" /> <p>收到來自父組件的消息:{{ $parent.message }}</p> <!--展示父組件實例的message--> </div> </template> <script> export default { name: 'Child1', data() { return { message: '', // 父組件通過this.$children可以獲取子組件實例的message } }, } </script>
// 父組件 <template> <div class="parent"> <h3>this is parent component</h3> <input type="text" v-model="message" /> <p>收到來自子組件的消息:{{ child1.message }}</p> <!--展示子組件實例的message--> <Child /> </div> </template> <script> import Child from './child' export default { name: 'Parent', data() { return { message: '', child1: {}, } }, components: { Child, }, mounted() { this.child1 = this.$children.find((child) => { return child.$options.name === 'Child1' // 通過options.name獲取對應name的child實例 }) }, } </script>
效果預覽
4. $attrs/$listener
簡介
$ attrs和 $ listeners 都是 Vue2.4 中新增加的屬性,主要是用來供使用者用來開發高級組件的。
$attrs:用來接收父作用域中不作為 prop 被識別的 attribute 屬性,並且可以通過 v-bind=”$attrs” 傳入內部組件——在創建高級別的組件時非常有用。
試想一下,當你創建瞭一個組件,你要接收 param1 、param2、param3 …… 等數十個參數,如果通過 props,那你需要通過 props: [‘param1’, ‘param2’, ‘param3’, ……] 等聲明一大堆。如果這些 props 還有一些需要往更深層次的子組件傳遞,那將會更加麻煩。
而使用 $attrs ,你不需要任何聲明,直接通過 $attrs.param1 、 $attrs.param2 ……就可以使用,而且向深層子組件傳遞上面也給瞭示例,十分方便。
$listeners:包含瞭父作用域中的 v-on 事件監聽器。它可以通過 v-on=”$listeners” 傳入內部組件——在創建更高層次的組件時非常有用,這裡在傳遞時的使用方法和 $attrs 十分類似。
代碼實例
在這個實例中,共有三個組件:A、B、C,其關系為:[ A [ B [C] ] ],A為B的父組件,B為C的父組件。即:1級組件A,2級組件B,3級組件C。我們實現瞭:
父向子傳值:1級組件A通過 :messageFromA=”message” 將 message 屬性傳遞給2級組件B,2級組件B通過 $attrs.messageFromA 獲取到1級組件A的 message 。
:messageFromA="message" v-bind="$attrs" $attrs.messageFromA
子向父傳值:1級組件A通過 @keyup=”receive” 在子孫組件上綁定keyup事件的監聽,2級組件B在通過 v-on=”$listeners” 來將 keyup 事件綁定在其 input 標簽上。當2級組件B input 輸入框輸入時,便會觸發1級組件A的receive回調,將2級組件B的 input 輸入框中的值賦值給1級組件A的 messageFromComp ,從而實現子向父傳值。
@keyup="receive" <CompC v-on="$listeners" /> v-on="$listeners"
代碼如下:
// 3級組件C <template> <div class="compc"> <h5>this is C component</h5> <input name="compC" type="text" v-model="message" v-on="$listeners" /> <!--將A組件keyup的監聽回調綁在該input上--> <p>收到來自A組件的消息:{{ $attrs.messageFromA }}</p> </div> </template> <script> export default { name: 'Compc', data() { return { message: '', } }, } </script>
// 2級組件B <template> <div class="compb"> <h4>this is B component</h4> <input name="compB" type="text" v-model="message" v-on="$listeners" /> <!--將A組件keyup的監聽回調綁在該input上--> <p>收到來自A組件的消息:{{ $attrs.messageFromA }}</p> <CompC v-bind="$attrs" v-on="$listeners" /> <!--將A組件keyup的監聽回調繼續傳遞給C組件,將A組件傳遞的attrs繼續傳遞給C組件--> </div> </template> <script> import CompC from './compC' export default { name: 'CompB', components: { CompC, }, data() { return { message: '', } }, } </script>
// A組件 <template> <div class="compa"> <h3>this is A component</h3> <input type="text" v-model="message" /> <p>收到來自{{ comp }}的消息:{{ messageFromComp }}</p> <CompB :messageFromA="message" @keyup="receive" /> <!--監聽子孫組件的keyup事件,將message傳遞給子孫組件--> </div> </template> <script> import CompB from './compB' export default { name: 'CompA', data() { return { message: '', messageFromComp: '', comp: '', } }, components: { CompB, }, methods: { receive(e) { // 監聽子孫組件keyup事件的回調,並將keyup所在input輸入框的值賦值給messageFromComp this.comp = e.target.name this.messageFromComp = e.target.value }, }, } </script>
效果預覽
5. provide/inject
簡介
provide/inject這對選項需要一起使用,以允許一個祖先組件向其所有子孫後代註入一個依賴,不論組件層次有多深,並在其上下遊關系成立的時間裡始終生效。如果你是熟悉React的同學,你一定會立刻想到Context這個api,二者是十分相似的。
provide:是一個對象,或者是一個返回對象的函數。該對象包含可註入其子孫的 property ,即要傳遞給子孫的屬性和屬性值。
injcet:一個字符串數組,或者是一個對象。當其為字符串數組時,使用方式和props十分相似,隻不過接收的屬性由data變成瞭provide中的屬性。當其為對象時,也和props類似,可以通過配置default和from等屬性來設置默認值,在子組件中使用新的命名屬性等。
代碼實例
這個實例中有三個組件,1級組件A,2級組件B,3級組件C:[ A [ B [C] ] ],A是B的父組件,B是C的父組件。實例中實現瞭:
父向子傳值:1級組件A通過provide將message註入給子孫組件,2級組件B通過 inject: [‘messageFromA’] 來接收1級組件A中的message,並通過 messageFromA.content 獲取1級組件A中message的content屬性值。
跨級向下傳值:1級組件A通過provide將message註入給子孫組件,3級組件C通過 inject: [‘messageFromA’] 來接收1級組件A中的message,並通過 messageFromA.content 獲取1級組件A中message的content屬性值,實現跨級向下傳值。
代碼如下:
// 1級組件A <template> <div class="compa"> <h3>this is A component</h3> <input type="text" v-model="message.content" /> <CompB /> </div> </template> <script> import CompB from './compB' export default { name: 'CompA', provide() { return { messageFromA: this.message, // 將message通過provide傳遞給子孫組件 } }, data() { return { message: { content: '', }, } }, components: { CompB, }, } </script>
// 2級組件B <template> <div class="compb"> <h4>this is B component</h4> <p>收到來自A組件的消息:{{ messageFromA && messageFromA.content }}</p> <CompC /> </div> </template> <script> import CompC from './compC' export default { name: 'CompB', inject: ['messageFromA'], // 通過inject接受A中provide傳遞過來的message components: { CompC, }, } </script>
// 3級組件C <template> <div class="compc"> <h5>this is C component</h5> <p>收到來自A組件的消息:{{ messageFromA && messageFromA.content }}</p> </div> </template> <script> export default { name: 'Compc', inject: ['messageFromA'], // 通過inject接受A中provide傳遞過來的message } </script>
註意點:
可能有同學想問我上面1級組件A中的message為什麼要用object類型而不是string類型,因為在vue provide 和 inject 綁定並不是可響應的。如果message是string類型,在1級組件A中通過input輸入框改變message值後無法再賦值給messageFromA,如果是object類型,當對象屬性值改變後,messageFromA裡面的屬性值還是可以隨之改變的,子孫組件inject接收到的對象屬性值也可以相應變化。
子孫provide和祖先同樣的屬性,會在後代中覆蓋祖先的provide值。例如2級組件B中也通過provide向3級組件C中註入一個messageFromA的值,則3級組件C中的messageFromA會優先接收2級組件B註入的值而不是1級組件A。
效果預覽
6. eventBus
簡介
eventBus又稱事件總線,通過註冊一個新的Vue實例,通過調用這個實例的 $ emit 和 $ on等來監聽和觸發這個實例的事件,通過傳入參數從而實現組件的全局通信。它是一個不具備 DOM 的組件,有的僅僅隻是它實例方法而已,因此非常的輕便。
我們可以通過在全局Vue實例上註冊:
// main.js Vue.prototype.$Bus = new Vue()
但是當項目過大時,我們最好將事件總線抽象為單個文件,將其導入到需要使用的每個組件文件中。這樣,它不會污染全局命名空間:
// bus.js,使用時通過import引入 import Vue from 'vue' export const Bus = new Vue()
原理分析
eventBus的原理其實比較簡單,就是使用訂閱-發佈模式,實現 $ emit 和 $ on兩個方法即可:
// eventBus原理 export default class Bus { constructor() { this.callbacks = {} } $on(event, fn) { this.callbacks[event] = this.callbacks[event] || [] this.callbacks[event].push(fn) } $emit(event, args) { this.callbacks[event].forEach((fn) => { fn(args) }) } } // 在main.js中引入以下 // Vue.prototype.$bus = new Bus()
代碼實例
在這個實例中,共包含瞭4個組件:[ A [ B [ C、D ] ] ],1級組件A,2級組件B,3級組件C和3級組件D。我們通過使用eventBus實現瞭:
全局通信:即包括瞭父子組件相互通信、兄弟組件相互通信、跨級組件相互通信。4個組件的操作邏輯相同,都是在input輸入框時,通過 this.$bus.$emit(‘sendMessage’, obj) 觸發sendMessage事件回調,將sender和message封裝成對象作為參數傳入;同時通過 this.$bus.$on(‘sendMessage’, obj) 監聽其他組件的sendMessage事件,實例當前組件示例sender和message的值。這樣任一組件input輸入框值改變時,其他組件都能接收到相應的信息,實現全局通信。
代碼如下:
// main.js Vue.prototype.$bus = new Vue()
// 1級組件A <template> <div class="containerA"> <h2>this is CompA</h2> <input type="text" v-model="message" @keyup="sendMessage" /> <p v-show="messageFromBus && sender !== $options.name"> 收到{{ sender }}的消息:{{ messageFromBus }} </p> <CompB /> </div> </template> <script> import CompB from './compB' export default { name: 'CompA', components: { CompB, }, data() { return { message: '', messageFromBus: '', sender: '', } }, mounted() { this.$bus.$on('sendMessage', (obj) => { // 通過eventBus監聽sendMessage事件 const { sender, message } = obj this.sender = sender this.messageFromBus = message }) }, methods: { sendMessage() { this.$bus.$emit('sendMessage', { // 通過eventBus觸發sendMessage事件 sender: this.$options.name, message: this.message, }) }, }, } </script>
// 2級組件B <template> <div class="containerB"> <h3>this is CompB</h3> <input type="text" v-model="message" @keyup="sendMessage" /> <p v-show="messageFromBus && sender !== $options.name"> 收到{{ sender }}的消息:{{ messageFromBus }} </p> <CompC /> <CompD /> </div> </template> <script> import CompC from './compC' import CompD from './compD' export default { name: 'CompB', components: { CompC, CompD, }, data() { return { message: '', messageFromBus: '', sender: '', } }, mounted() { this.$bus.$on('sendMessage', (obj) => { // 通過eventBus監聽sendMessage事件 const { sender, message } = obj this.sender = sender this.messageFromBus = message }) }, methods: { sendMessage() { this.$bus.$emit('sendMessage', { // 通過eventBus觸發sendMessage事件 sender: this.$options.name, message: this.message, }) }, }, } </script>
// 3級組件C <template> <div class="containerC"> <p>this is CompC</p> <input type="text" v-model="message" @keyup="sendMessage" /> <p v-show="messageFromBus && sender !== $options.name"> 收到{{ sender }}的消息:{{ messageFromBus }} </p> </div> </template> <script> export default { name: 'CompC', data() { return { message: '', messageFromBus: '', sender: '', } }, mounted() { this.$bus.$on('sendMessage', (obj) => { // 通過eventBus監聽sendMessage事件 const { sender, message } = obj this.sender = sender this.messageFromBus = message }) }, methods: { sendMessage() { this.$bus.$emit('sendMessage', { // 通過eventBus觸發sendMessage事件 sender: this.$options.name, message: this.message, }) }, }, } </script>
// 3級組件D <template> <div class="containerD"> <p>this is CompD</p> <input type="text" v-model="message" @keyup="sendMessage" /> <p v-show="messageFromBus && sender !== $options.name"> 收到{{ sender }}的消息:{{ messageFromBus }} </p> </div> </template> <script> export default { name: 'CompD', data() { return { message: '', messageFromBus: '', sender: '', } }, mounted() { this.$bus.$on('sendMessage', (obj) => { // 通過eventBus監聽sendMessage事件 const { sender, message } = obj this.sender = sender this.messageFromBus = message }) }, methods: { sendMessage() { this.$bus.$emit('sendMessage', { // 通過eventBus觸發sendMessage事件 sender: this.$options.name, message: this.message, }) }, }, } </script>
效果預覽
圖片過大,截圖處理
7. Vuex
當項目龐大以後,在多人維護同一個項目時,如果使用事件總線進行全局通信,容易讓全局的變量的變化難以預測。於是有瞭Vuex的誕生。
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
有關Vuex的內容,可以參考 Vuex官方文檔 [1] ,我就不在這裡班門弄斧瞭,直接看代碼。
代碼實例
Vuex的實例和事件總線leisi,同樣是包含瞭4個組件:[ A [ B [ C、D ] ] ],1級組件A,2級組件B,3級組件C和3級組件D。我們在這個實例中實現瞭:
全局通信:代碼的內容和eventBus也類似,不過要比eventBus使用方便很多。每個組件通過watch監聽input輸入框的變化,把input的值通過vuex的commit觸發mutations,從而改變stroe的值。然後每個組件都通過computed動態獲取store中的數據,從而實現全局通信。
// store.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { message: { sender: '', content: '', }, }, mutations: { sendMessage(state, obj) { state.message = { sender: obj.sender, content: obj.content, } }, }, })
// 組件A <template> <div class="containerA"> <h2>this is CompA</h2> <input type="text" v-model="message" /> <p v-show="messageFromStore && sender !== $options.name"> 收到{{ sender }}的消息:{{ messageFromStore }} </p> <CompB /> </div> </template> <script> import CompB from './compB' export default { name: 'CompA', components: { CompB, }, data() { return { message: '', } }, computed: { messageFromStore() { return this.$store.state.message.content }, sender() { return this.$store.state.message.sender }, }, watch: { message(newValue) { this.$store.commit('sendMessage', { sender: this.$options.name, content: newValue, }) }, }, } </script>
同樣和eventBus中一樣,B,C,D組件中的代碼除瞭引入子組件的不同,script部分都是一樣的,就不再往上寫瞭。
效果預覽
總結
上面總共提到瞭7中Vue的組件通信方式,他們能夠進行的通信種類如下圖所示:
- props/$emit:可以實現父子組件的雙向通信,在日常的父子組件通信中一般會作為我們的最常用選擇。
- v-slot:可以實現父子組件單向通信(父向子傳值),在實現可復用組件,向組件中傳入DOM節點、html等內容以及某些組件庫的表格值二次處理等情況時,可以優先考慮v-slot。
- $ refs/$ parent/ $ children/ $ r oot: 可 以實現父子組件雙向通信,其中 $root可以實現根組件實例向子孫組件跨級單向傳值。 在父組件沒有傳遞值或通過v-on綁定監聽時,父子間想要獲取彼此的屬性或方法可以考慮使用這些api。
- $ attrs/ $ listeners: 能夠實現跨級雙向通信,能夠讓你簡單的獲取傳入的屬性和綁定的監聽,並且方便地向下級子組件傳遞,在構建高級組件時十分好用。
- provide/inject:可以實現跨級單向通信,輕量地向子孫組件註入依賴,這是你在實現高級組件、創建組件庫時的不二之選。
- eventBus:可以實現全局通信,在項目規模不大的情況下,可以利用eventBus實現全局的事件監聽。但是eventBus要慎用,避免全局污染和內存泄漏等情況。
- Vuex:可以實現全局通信,是vue項目全局狀態管理的最佳實踐。在項目比較龐大,想要集中式管理全局組件狀態時,那麼安裝Vuex準沒錯!
以上就是深入瞭解Vue組件七種通信方式的詳細內容,更多關於Vue組件通信方式的資料請關註WalkonNet其它相關文章!