深入瞭解Vue元件七種通訊方式
目錄
- 1. props/$emit
- 簡介
- 程式碼例項
- 2. v-slot
- 簡介
- 程式碼例項
- 3. $refs/ $parent/ $children/$root
- 簡介
- 程式碼例項
- 4. $attrs/$listener
- 簡介
- 程式碼例項
- 5. provide/inject
- 簡介
- 程式碼例項
- 6. eventBus
- 簡介
- 原理分析
- 程式碼例項
- 7. x
- 程式碼例項
- 總結
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: '',} </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',// 父元件通過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',child1: {},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',} </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="$l客棧isteners" /> <!--將A元件keyup的監聽回撥繼續傳遞給C元件,將A元件傳遞的attrs繼續傳遞給C元件-->
</div>
</template>
<script>
import CompC from './compC'
export default {
name: 'CompB',components: {
CompC,}
</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',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() { http://www.cppcns.comreturn { message: { content: '',} </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接受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. 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,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,}) },} </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: '',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,}) www.cppcns.com },} </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: '',message } = obj this.sender = sender this.messageFromBus = 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',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,www.cppcns.com 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元件通訊方式的資料請關注我們其它相關文章!