vue是數(shù)據(jù)驅(qū)動(dòng)視圖更新的框架狐蜕,所以對于vue來說組件間的數(shù)據(jù)通信非常重要。組件是vue的強(qiáng)大功能之一卸夕,而組件間的作用域是互相獨(dú)立的层释,這就意味著組件間的數(shù)據(jù)無法互相引用,那么組件間如何通信便成為了重點(diǎn)知識(shí)快集。本文將分析不同組件間通信的各種方式贡羔。
prop/ $emit
父組件通過prop向子組件傳遞數(shù)據(jù)廉白,而子組件通過$emit向父組件通信
//父組件
<template>
<Child :text=text @change="changeCurrentText"></Child>
</template>
<script>
import Child from './child.vue'
export default {
data(){
return{
text: 'sss'
}
},
components:{ Child },
methods: {
changeCurrentText(value){
this.text = value
}
}
}
</script>
//child.vue
<template>
<div @click="changeText">
{{text}}
</div>
</template>
<script>
export default {
props:{
text: {
type: String
}
},
methods:{
changeText(){
this.$emit('change','改變了')
}
}
}
</script>
父子組件之間的數(shù)據(jù)傳遞只能從父組件傳向子組件,這正是vue的單向數(shù)據(jù)流設(shè)計(jì)理念乖寒。而prop在其中充當(dāng)了數(shù)據(jù)傳遞的一個(gè)銜接口蒙秒,在子組件中我們可以在props中定義接收子組件值的類型和默認(rèn)值。
這種通信方式是父子通信最常用的宵统,傳值取值方便簡潔明了晕讲,但是這種方式的缺點(diǎn)是:
- 由于數(shù)據(jù)是單向傳遞,如果子組件需要改變父組件的props值每次需要給子組件綁定對應(yīng)的監(jiān)聽事件马澈。
- 如果父組件需要給孫組件傳值瓢省,需要子組件進(jìn)行轉(zhuǎn)發(fā)。
注:組件中的數(shù)據(jù)共有三種形式:data痊班、props勤婚、computed
ref
ref被用來給元素或子組件注冊引用信息,引用信息將會(huì)注冊在父組件的$refs對象上涤伐,若注冊了多個(gè)ref引用馒胆,同樣都將會(huì)被注冊在父組件的$refs對象上,如果在普通的DOM元素上使用凝果,引用指向的就是DOM元素祝迂;如果用在子組件上,引用就指向組件實(shí)例器净。然后通過$refs可以獲得你綁定的這個(gè)組件或者元素的一些相關(guān)信息型雳,還可以修改子組件的值。
//父組件:
<template>
<div>
<Child ref="a" />
<input type="button" @click="fn">
</div>
</template>
<script>
import Child from "./Child"
export default{
methods:{
fn(){
this.$refs["a"].sun=10
console.log(this.$refs["a"].sun)//10
}
},
components:{
Child
},
mounted(){
console.log(this.$refs["a"].sun)//1
}
}
</script>
//子組件:
<template>
<div>
子組件
</div>
</template>
<script>
export default{
data(){
return{
sun:1
}
}
}
</script>
$attrs/ $listeners
多級(jí)組件嵌套時(shí)可使用
$attrs: 只代表沒有被聲明未props的屬性山害,如果某個(gè)屬性已存在于子組件的props中則$attrs會(huì)將該屬性剔除纠俭,$attrs是組件標(biāo)簽上的靜態(tài)屬性值(attr)和動(dòng)態(tài)屬性值(:attr)的對象集合。
//父組件A
<template>
<B :name='name' :age='age'/>
</template>
import B from './B.vue'
export default {
data() {
return {
name:"小紅",
age:"18"
}
},
components:{ B },
}
//子組件B
<template>
<div>
<div>{{name}}</div>
<div>{{$attrs.age}}</div>
//通過v-bind 綁定$attrs屬性浪慌,C組件可以直接獲取到A組件中傳遞下來的props(除了B組件中props聲明的)
<C v-bind="$attrs" v-on="$listeners"/>
</div>
</template>
import C from './C.vue'
export default {
props:["name"]
data() {
return { }
},
components:{ C },
}
//孫組件C
<template>
<div>age:{{$attrs.age}}</div>
</template>
<script>
export default {
}
</script>
$listeners 包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽器冤荆,它可以通過 v-on=”$listeners” 傳入內(nèi)部組件。
//父組件A
<template>
<B :name='name' :age='age' v-on:testB="onB" v-on:testC="onC" />
</template>
import B from './B.vue'
export default {
data() {
return {
name:"小紅",
age:"18"
}
},
components:{ B },
methods: {
//b組件傳回來的數(shù)據(jù)
onB(msg) {
console.log('我是B:',msg);
},
// c組件傳回來的數(shù)據(jù)
onC(msg) {
console.log('我是C:',msg);
}
}
}
//子組件B
<template>
<div>
<div>{{name}}</div>
//C組件中能直接觸發(fā)test的原因在于 B組件調(diào)用C組件時(shí) 使用 v-on 綁定了$listeners 屬性
<C v-bind="$attrs" v-on="$listeners"/>
</div>
</template>
import C from './C.vue'
export default {
props:["name"]
data() {
return { }
},
components:{ C },
mounted() {
this.$emit('testB','BBBB');
}
}
//孫組件C
<template>
<div>age:{{$attrs.age}}</div>
</template>
<script>
export default {
mounted() {
this.$emit('testC','CCCC');
}
}
</script>
簡單來說:$attrs與$listeners 是兩個(gè)對象权纤,$attrs 里存放的是父組件中綁定的非 Props 屬性钓简,$listeners里存放的是父組件中綁定的非原生事件。
provide/ inject
provide 和 inject 綁定并不是可響應(yīng)的妖碉,如果你傳入了一個(gè)可監(jiān)聽的對象涌庭,那么其對象的屬性還是可響應(yīng)的。
provide 可以在祖先組件中指定我們想要提供給后代組件的數(shù)據(jù)或方法欧宜,而在任何后代組件中坐榆,我們都可以使用 inject 來接收 provide 提供的數(shù)據(jù)或方法。
// 父組件
<template>
<div>
<div>{{foo}}</div>
<B/>
</div>
</template>
<script>
import B from "./B";
export default {
components: { B },
provide() {
return {
foo: this.foo
};
},
data() {
return {
foo: "父組件",
};
}冗茸,
mounted() {
console.log(this.foo)
},
};
</script>
//子級(jí)組件
<template>
<C/>
</template>
<script>
import C from "./C";
export default {
components: { C },
};
</script>
//孫級(jí)組件席镀,接收foo
<template>
<div>{{foo}}</div>
</template>
<script>
export default {
inject: ["foo"],
mounted() {
console.log(this.foo)
},
};
</script>
將數(shù)據(jù)變成可響應(yīng)式的解決方案是把provide所在的Vue實(shí)例給傳遞下去匹中,再來改造一下
provide() {
return {
foo: this.foo
};
},
data() {
return {
foo: {
foo: "父組件"
}
在這種情況下如果在孫組件改變inject中foo的值,也會(huì)響應(yīng)的更新到父組件中豪诲,當(dāng)然為了保護(hù)單向數(shù)據(jù)流機(jī)制顶捷,最佳實(shí)踐還是不要在子組件里更改inject。
在進(jìn)行組件庫開發(fā)時(shí)provide/inject是一個(gè)不錯(cuò)的選擇屎篱,但是當(dāng)多個(gè)后代組件同時(shí)依賴同一個(gè)父組件提供數(shù)據(jù)時(shí)服赎,只要任一組件對數(shù)據(jù)進(jìn)行了修改,所有依賴的組件都會(huì)受到影響交播,實(shí)際上是增加了耦合度重虑;任意層級(jí)訪問使數(shù)據(jù)追蹤變的比較困難,你并不能準(zhǔn)確的定位到是哪一個(gè)層級(jí)對數(shù)據(jù)進(jìn)行了改變秦士,當(dāng)數(shù)據(jù)出現(xiàn)問題時(shí)缺厉,尤其是多人協(xié)作時(shí),可能會(huì)大大增加問題定位的損耗隧土。
EventBus
EventBus又稱事件總線提针,相當(dāng)于一個(gè)全局的倉庫,任何組件都可以去這個(gè)倉庫里獲取事件
const eventBus = new Vue();
eventBus.$emit(eventName, […args]) //發(fā)布事件
eventBus.$on(event, callback) //訂閱事件
//實(shí)例如下:
// eventBus.js
import Vue from 'vue'
export const eventBus = new Vue();
//另外一種方式曹傀,可以直接在項(xiàng)目中的 main.js 初始化 EventBus(這種方式初始化的EventBus是一個(gè)全局的事件總線) :
Vue.prototype.$EventBus = new Vue()
//a.vue發(fā)送事件
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
import { EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", '來自A頁面的消息');
}
}
};
</script>
//b.vue接收事件
<template>
<p>{{msg}}</p>
</template>
<script>
import {
EventBus
} from "../event-bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("aMsg", (msg) => {
// A發(fā)送來的消息
this.msg = msg;
});
}
};
</script>
vue是單頁應(yīng)用辐脖,如果你在某一個(gè)頁面刷新了之后,與之相關(guān)的EventBus會(huì)被移除卖毁;如果是業(yè)務(wù)有反復(fù)操作的頁面揖曾,EventBus在監(jiān)聽的時(shí)候就會(huì)觸發(fā)很多次落萎,所以通常在vue頁面銷毀時(shí)亥啦,同時(shí)移除EventBus事件監(jiān)聽。
//移除事件監(jiān)聽
import {
eventBus
} from './event-bus.js'
EventBus.$off('aMsg', {})
也可以使用 EventBus.$off('aMsg') 來移除應(yīng)用內(nèi)所有對此某個(gè)事件的監(jiān)聽
全局EventBus的工作原理是發(fā)布/訂閱方法练链,通常稱為 Pub/Sub 翔脱。
//創(chuàng)建全局EventBus
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
在這個(gè)特定的總線中使用兩個(gè)方法$on和$emit。一個(gè)用于創(chuàng)建發(fā)出的事件媒鼓,它就是$emit届吁;另一個(gè)用于訂閱$on:
var EventBus = new Vue();
this.$bus.$emit('nameOfEvent', { ... pass some event data ...});
this.$bus.$on('nameOfEvent',($event) => {
// ...
})
然后我們可以在某個(gè)Vue頁面使用this.$bus.$emit("sendMsg", 'emit....');,另一個(gè)Vue頁面使用
this.$bus.$on('updateMessage', function(value) {
console.log(value); // emit....
})
同時(shí)也可以使用this.$bus.$off('sendMsg')來移除事件監(jiān)聽绿鸣。
vuex
Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式