
作者:小科比_來源:https://juejin.cn/post/6887709516616433677
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><divclass="child"><h4>thisischildcomponent</h4><inputtype="text"v-model="message"@keyup="send"/><p>收到來自父組件的消息:{{ messageFromParent }}</p></div></template><script>exportdefault{name:'Child',props:['messageFromParent'],//通過props接收父組件傳過來的消息data(){return{message:'',}},methods:{send(){this.$emit('on-receive',this.message)//通過$emit觸發on-receive事件,調用父組件中receive回調,並將this.message作為參數},},}</script>//父組件代碼<template><divclass="parent"><h3>thisisparentcomponent</h3><inputtype="text"v-model="message"/><p>收到來自子組件的消息:{{ messageFromChild }}</p><Child:messageFromParent="message"@on-receive="receive"/></div></template><script>importChildfrom'./child'exportdefault{name:'Parent',data(){return{message:'',//傳遞給子組件的消息messageFromChild:'',}},components:{Child,},methods:{receive(msg){//接受子組件的信息,並將其賦值給messageFromChildthis.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><divclass="child"><h4>thisischildcomponent</h4><p>收到來自父組件的消息:<slotname="child"></slot><!--展示父組件通過插槽傳遞的{{message}}--></p></div></template><template><divclass="parent"><h3>thisisparentcomponent</h3><inputtype="text"v-model="message"/><Child><templatev-slot:child>{{message}}<!--插槽要展示的內容--></template></Child></div></template><script>importChildfrom'./child'exportdefault{name:'Parent',data(){return{message:'',}},components:{Child,},}</script>效果預覽3.parent/root簡介
我們也同樣可以通過$refs/$parent/$children/$root等方式獲取 Vue 組件實例,得到實例上綁定的屬性及方法等,來實現組件之間的通信。
$refs:我們通常會將refs 綁定在子組件上,從而獲取子組件實例。
:我們可以在中直接通過parent`來獲取當前組件的父組件實例(如果有的話)。
children來獲取當前組件的子組件實例的數組。但是需要注意的是,this.$children`數組中的元素下標並不一定對用父組件引用的子組件的順序,例如有異步加載的子組件,可能影響其在 children 數組中的順序。所以使用時需要根據一定的條件例如子組件的name去找到相應的子組件。
$root:獲取當前組件樹的根 Vue 實例。如果當前實例沒有父實例,此實例將會是其自己。通過 $root ,我們可以實現組件之間的跨級通信。
代碼實例
下面來看一個和children 使用的實例(由於這幾個api的使用方式大同小異,所以關於和root 的使用就不在這裡展開了,在這個實例中實現了:
代碼如下:
//子組件<template><divclass="child"><h4>thisischildcomponent</h4><inputtype="text"v-model="message"/><p>收到來自父組件的消息:{{$parent.message }}</p><!--展示父組件實例的message--></div></template><script>exportdefault{name:'Child1',data(){return{message:'',//父組件通過this.$children可以獲取子組件實例的message}},}</script>//父組件<template><divclass="parent"><h3>thisisparentcomponent</h3><inputtype="text"v-model="message"/><p>收到來自子組件的消息:{{ child1.message }}</p><!--展示子組件實例的message--><Child/></div></template><script>importChildfrom'./child'exportdefault{name:'Parent',data(){return{message:'',child1:{},}},components:{Child,},mounted(){this.child1=this.$children.find((child)=>{returnchild.$options.name==='Child1'//通過options.name獲取對應name的child實例})},}</script>效果預覽4.listener 前端開發博客簡介
和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。我們實現了:
代碼如下:
//3級組件C<template><divclass="compc"><h5>thisisCcomponent</h5><inputname="compC"type="text"v-model="message"v-on="$listeners"/><!--將A組件keyup的監聽回調綁在該input上--><p>收到來自A組件的消息:{{$attrs.messageFromA }}</p></div></template><script>exportdefault{name:'Compc',data(){return{message:'',}},}</script>//2級組件B<template><divclass="compb"><h4>thisisBcomponent</h4><inputname="compB"type="text"v-model="message"v-on="$listeners"/><!--將A組件keyup的監聽回調綁在該input上--><p>收到來自A組件的消息:{{$attrs.messageFromA }}</p><CompCv-bind="$attrs"v-on="$listeners"/><!--將A組件keyup的監聽回調繼續傳遞給C組件,將A組件傳遞的attrs繼續傳遞給C組件--></div></template><script>importCompCfrom'./compC'exportdefault{name:'CompB',components:{CompC,},data(){return{message:'',}},}</script>//A組件<template><divclass="compa"><h3>thisisAcomponent</h3><inputtype="text"v-model="message"/><p>收到來自{{ comp }}的消息:{{ messageFromComp }}</p><CompB:messageFromA="message"@keyup="receive"/><!--監聽子孫組件的keyup事件,將message傳遞給子孫組件--></div></template><script>importCompBfrom'./compB'exportdefault{name:'CompA',data(){return{message:'',messageFromComp:'',comp:'',}},components:{CompB,},methods:{receive(e){//監聽子孫組件keyup事件的回調,並將keyup所在input輸入框的值賦值給messageFromCompthis.comp=e.target.namethis.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<template><divclass="compa"><h3>thisisAcomponent</h3><inputtype="text"v-model="message.content"/><CompB/></div></template><script>importCompBfrom'./compB'exportdefault{name:'CompA',provide(){return{messageFromA:this.message,//將message通過provide傳遞給子孫組件}},data(){return{message:{content:'',},}},components:{CompB,},}</script>//2級組件B<template><divclass="compb"><h4>thisisBcomponent</h4><p>收到來自A組件的消息:{{ messageFromA && messageFromA.content }}</p><CompC/></div></template><script>importCompCfrom'./compC'exportdefault{name:'CompB',inject:['messageFromA'],//通過inject接受A中provide傳遞過來的messagecomponents:{CompC,},}</script>//3級組件C<template><divclass="compc"><h5>thisisCcomponent</h5><p>收到來自A組件的消息:{{ messageFromA && messageFromA.content }}</p></div></template><script>exportdefault{name:'Compc',inject:['messageFromA'],//通過inject接受A中provide傳遞過來的message}</script>
注意點:
效果預覽6. eventBus簡介
eventBus又稱事件總線,通過註冊一個新的Vue實例,通過調用這個實例的和on等來監聽和觸發這個實例的事件,通過傳入參數從而實現組件的全局通信。它是一個不具備 DOM 的組件,有的僅僅只是它實例方法而已,因此非常的輕便。我們可以通過在全局Vue實例上註冊:
//main.jsVue.prototype.$Bus=newVue()
但是當項目過大時,我們最好將事件總線抽象為單個文件,將其導入到需要使用的每個組件文件中。這樣,它不會污染全局命名空間:
//bus.js,使用時通過import引入importVuefrom'vue'exportconstBus=newVue()原理分析
eventBus的原理其實比較簡單,就是使用訂閱-發布模式,實現和on兩個方法即可:
//eventBus原理exportdefaultclassBus{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=newBus()代碼實例
在這個實例中,共包含了4個組件:[ A [ B [ C、D ] ] ],1級組件A,2級組件B,3級組件C和3級組件D。我們通過使用eventBus實現了:
代碼如下:
//main.jsVue.prototype.$bus=newVue()//1級組件A<template><divclass="containerA"><h2>thisisCompA</h2><inputtype="text"v-model="message"@keyup="sendMessage"/><pv-show="messageFromBus&&sender!==$options.name">收到{{ sender }}的消息:{{ messageFromBus }}</p><CompB/></div></template><script>importCompBfrom'./compB'exportdefault{name:'CompA',components:{CompB,},data(){return{message:'',messageFromBus:'',sender:'',}},mounted(){this.$bus.$on('sendMessage',(obj)=>{//通過eventBus監聽sendMessage事件const{sender,message}=objthis.sender=senderthis.messageFromBus=message})},methods:{sendMessage(){this.$bus.$emit('sendMessage',{//通過eventBus觸發sendMessage事件sender:this.$options.name,message:this.message,})},},}</script>//2級組件B<template><divclass="containerB"><h3>thisisCompB</h3><inputtype="text"v-model="message"@keyup="sendMessage"/><pv-show="messageFromBus&&sender!==$options.name">收到{{ sender }}的消息:{{ messageFromBus }}</p><CompC/><CompD/></div></template><script>importCompCfrom'./compC'importCompDfrom'./compD'exportdefault{name:'CompB',components:{CompC,CompD,},data(){return{message:'',messageFromBus:'',sender:'',}},mounted(){this.$bus.$on('sendMessage',(obj)=>{//通過eventBus監聽sendMessage事件const{sender,message}=objthis.sender=senderthis.messageFromBus=message})},methods:{sendMessage(){this.$bus.$emit('sendMessage',{//通過eventBus觸發sendMessage事件sender:this.$options.name,message:this.message,})},},}</script>//3級組件C<template><divclass="containerC"><p>thisisCompC</p><inputtype="text"v-model="message"@keyup="sendMessage"/><pv-show="messageFromBus&&sender!==$options.name">收到{{ sender }}的消息:{{ messageFromBus }}</p></div></template><script>exportdefault{name:'CompC',data(){return{message:'',messageFromBus:'',sender:'',}},mounted(){this.$bus.$on('sendMessage',(obj)=>{//通過eventBus監聽sendMessage事件const{sender,message}=objthis.sender=senderthis.messageFromBus=message})},methods:{sendMessage(){this.$bus.$emit('sendMessage',{//通過eventBus觸發sendMessage事件sender:this.$options.name,message:this.message,})},},}</script>//3級組件D<template><divclass="containerD"><p>thisisCompD</p><inputtype="text"v-model="message"@keyup="sendMessage"/><pv-show="messageFromBus&&sender!==$options.name">收到{{ sender }}的消息:{{ messageFromBus }}</p></div></template><script>exportdefault{name:'CompD',data(){return{message:'',messageFromBus:'',sender:'',}},mounted(){this.$bus.$on('sendMessage',(obj)=>{//通過eventBus監聽sendMessage事件const{sender,message}=objthis.sender=senderthis.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。我們在這個實例中實現了:
//store.jsimportVuefrom'vue'importVuexfrom'vuex'Vue.use(Vuex)exportdefaultnewVuex.Store({state:{message:{sender:'',content:'',},},mutations:{sendMessage(state,obj){state.message={sender:obj.sender,content:obj.content,}},},})//組件A<template><divclass="containerA"><h2>thisisCompA</h2><inputtype="text"v-model="message"/><pv-show="messageFromStore&&sender!==$options.name">收到{{ sender }}的消息:{{ messageFromStore }}</p><CompB/></div></template><script>importCompBfrom'./compB'exportdefault{name:'CompA',components:{CompB,},data(){return{message:'',}},computed:{messageFromStore(){returnthis.$store.state.message.content},sender(){returnthis.$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的組件通信方式,他們能夠進行的通信種類如下圖所示:
最後,魯迅說過:「一碗酸辣湯,耳聞口講的,總不如親自呷一口的明白。」(魯迅:這句話我真說過!)看了這麼多,不如自己親手去敲一敲更能理解,看完可以去手動敲一敲加深理解。
(文本完)

每日分享前端插件乾貨,歡迎關注!
點讚和在看就是最大的支持❤️