前言
Vue元件執行個體間的作用域是互相獨立的,而通常一個頁面是由很多個元件構成,這些元件可能又嵌套了元件,形成了一個關系網圖,它們的關系可能是像下圖中一樣,大緻分為兩種使用場景,父子元件間通信和非父子元件間通信,父子元件間通信又分為直接父子關系和間接父子關系。vue提供了多種通信方法,針對不同的通信需求,選擇最合适的通信方式能幫助我們提高開發效率。本文簡要介紹了九種通信方式以及适用場景,記錄下在學習中的收貨。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIjBXPt9mcm9jMkVmN2AzMmZjZkNmMlVGOwImN0gzYmFWZzkDNjV2Nl9CXwsWO0EHbyomdx1Sat42YtM3b09CXul2ZpJ3bvwVbvNmLn1WavFWa0V3b05iNyA3Lc9CX6MHc0RHaiojIsJye.jpg)
父子元件間的通信
props和$emit
這是父子元件在傳參時中常用的一種方式,父元件通過v-bind傳入參數,子元件通過props來接收,子元件通過内建的$emit方法傳入事件名來觸發一個事件,父元件通過v-on像監聽原生DOM事件一樣來監聽這個事件。
// 父元件CompFather
<template>
<div>
<comp-son
:title-name="title_name"
@changeTitle="changeTitle"
/>
<div>父元件的title_name:{{ title_name }}</div>
</div>
</template>
<script>
import CompSon from "./CompSon";
export default {
name: "CompFather",
components: {CompSon},
data() {
return {
title_name: "我是初始值"
}
},
methods: {
changeTitle(val) {
this.title_name = val;
}
}
}
</script>
複制代碼
// 子元件CompSon
<template>
<div>
<div>子元件的titleName:{{titleName}}</div>
<button @click="changeTitle">改變</button>
</div>
</template>
<script>
export default {
name: "CompSon",
props: {
titleName: {
type: String,
required: true
}
},
methods: {
changeTitle() {
this.$emit("changeTitle", "我變了");
}
}
}
</script>
複制代碼
适用場景:适用于直接父子關系(中間無嵌套元件)的元件間進行通信。
$attrs和$listeners
$attrs包含了父作用域中不作為 prop 被識别 (且擷取) 的 attribute 綁定 (class 和 style 除外)。當一個元件沒有聲明任何 prop 時,這裡會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="$attrs" 傳入内部元件。
// Comp1.vue
<template>
<comp2
:flag="flag"
:id="id"
:msg="msg"
/>
</template>
<script>
import Comp2 from "./comp2";
export default {
name: "comp1",
components: {Comp2},
data() {
return {
id: 1,
msg: "comp1's msg",
flag: true
}
}
}
</script>
複制代碼
// Comp2.vue
<template>
<div>
<div>comp2的$attrs:{{$attrs}}</div>
<comp3 v-bind="$attrs" />
</div>
</template>
<script>
import Comp3 from "./comp3";
export default {
name: "comp2",
components: {Comp3},
props: {
flag: Boolean
},
mounted() {
console.log(this.$attrs); // { "id": 1, "msg": "comp1's msg" }
}
}
</script>
複制代碼
// Comp3.vue
<template>
<div>comp3的$attrs:{{ $attrs }}</div>
</template>
<script>
export default {
name: "comp3",
props: {
msg: String
},
mounted() {
console.log(this.$attrs); // { "id": 1 }
}
}
</script>
複制代碼
這裡Comp2的props接收了Comp1傳入的flag,是以Comp2中的$attrs沒有flag參數,Comp3接收了msg,是以$attrs隻有id。
适用場景:元件之間跨級傳參,可以使用$attrs屬性,這樣使得代碼更加簡潔,更利于維護。
$listeners包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入内部元件。
當我們要封裝一個input類型的元件時,我們可以通過.native的方式監聽它的原生事件。
// App.vue
<my-input @input.native="inputData" :value="value" />
複制代碼
// MyInput.vue
<template>
<input
type="text"
:value="value"
@input="$emit('input',e.target.value)"
>
</template>
<script>
export default {
name: "MyInput",
props: {
value: String
},
}
</script>
複制代碼
但如果此時的input被label标簽包裹了一層,input不再是根元件時,父元件的.native将監聽失效,于是vue給我們提供了$listeners屬性來解決這個問題。
// MyInput.vue
<template>
<label>
value:{{ value }}
<input
type="text"
:value="value"
@input="$emit('input',e.target.value)"
>
</label>
</template>
複制代碼
以下是使用$listeners屬性進行監聽:
// App.vue
<template>
<my-input v-model="value" @focus="onFocus" @input="inputData" />
</template>
<script>
export default {
name: "App",
data() {
value: ""
},
onFocus() {
console.log("獲得焦點");
},
inputData(val) {
console.log(val);
}
}
</script>
複制代碼
<template>
<label>
value:{{ value }}
<input
type="text"
:value="value"
v-on="inputListeners"
>
</label>
</template>
<script>
export default {
name: "MyInput",
props: {
value: String
},
computed: {
inputListeners: function () {
const vm = this
// `Object.assign` 将所有的對象合并為一個新對象
return Object.assign({},
// 我們從父級添加所有的監聽器
this.$listeners,
// 然後我們添加自定義監聽器,
// 或覆寫一些監聽器的行為
{
// 這裡確定元件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
}
</script>
複制代碼
适用場景:當需要監聽子元件的原生事件,且子元件可能是經過重構的元件時,可以使用$listeners來代替.native監聽,當然也不隻是包含原生事件監聽器,隻是在這裡監聽原生事件比較合适,我們還有其它方式來做事件監聽。
$children/$parent/$root
$children是目前執行個體的直接子元件,不保證順序,數組順序不一定是子元件在該父元件中的渲染順序,也不是響應式的。
如果目前執行個體有父元件的話,$parent則是目前執行個體的父執行個體。
$root是目前元件樹的根 Vue 執行個體。如果目前執行個體沒有父執行個體,此執行個體将會是其自己。通過this.$root來通路。
<script>
export default {
name: "MyComp",
data() {
value: ""
},
mounted: {
console.log(this.$parent);
console.log(this.$children);
console.log(this.$root);
}
}
</script>
複制代碼
缺點:$children擷取到的執行個體不保證順序,是以當有多個子元件時可能擷取到的不是自己想要的那一個。當元件嵌套多級時,可能會出現$parent.$parent.$parent...的情況,對于後續維護不友好。
适用場景:盡量使用其它方式來代替$parent/$chilren
ref
ref 被用來給元素或子元件注冊引用資訊。引用資訊将會注冊在父元件的 $refs 對象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子元件上,引用就指向元件執行個體。
// App.vue
<template>
<comp1 ref="comp1"/>
</template>
<script>
export default {
name: "App",
data() {
value: ""
},
mounted: {
console.log("comp1的msg:",this.$refs.comp1.msg);
}
}
</script>
複制代碼
适用場景:當使用element-ui元件時,可用于調用元件方法,例如el-table元件的選擇表格項,排序等等。
v-model
一個元件上的 v-model 預設會利用名為 value 的 prop 和名為 input 的事件,但是像單選框、複選框等類型的輸入控件可能會将 value attribute 用于不同的目的。model 選項可以用來避免這樣的沖突。
// App.vue
<add-item v-model="input_val" />
複制代碼
<template>
<div class="add">
<input
:value="value"
type="text"
placeholder="What needs to be done?"
@input="inputVal"
>
</div>
</template>
<script>
export default {
name: "AddItem",
// 如果對應的props字段名不叫value,則需要定義model屬性來指定父元件的v-modal綁定的是哪個值
/*model: {
prop: "value1",
event: "input"
},*/
props: {
value: {
type: String,
required: true
}
},
methods: {
inputVal(e) {
this.$emit("input", e.target.value);
}
}
}
</script>
複制代碼
适用場景:v-model适用于在封裝input類型的元件時,用于給資料進行雙向綁定,如果不使用v-model,則需要在父元件增加一個方法來監聽事件然後改變父元件中的值,顯得非常麻煩且不簡潔。
Sync修飾符
sync與v-model非常類似,也是适用于需要子元件改變父元件的值時。 :isShow.sync="isShow"其實是 @update:isShow="bol=>isShow=bol"文法糖
<template>
<div>
<input type="button"
value="我是父元件中的按鈕"
@click="show">
<child :isShow.sync="isShow" v-show="isShow"/>
</div>
</template>
<script>
import child from "@/components/child"
export default {
data() {
return {
isShow:false
}
},
components:{
child
},
methods:{
show(){
this.isShow=true;
},
changeIsShow(bol){
this.isShow=bol;
}
}
}
</script>
複制代碼
<template>
<div>
我是一個子元件,我在紅色的海洋裡!
<input type="button" value="點我隐身" @click="upIsShow">
</div>
</template>
<script>
export default {
methods:{
upIsShow(){
this.$emit("update:isShow",false);
}
}
}
</script>
複制代碼
provide/inject
這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,并在其上下遊關系成立的時間裡始終生效。
provide在父元件中将子元件要使用的資料抛出,然後在子元件中inject注入要使用的資料,它不像使用props需要層層傳遞,中間不需要這個資料的地方也需要聲明,provide和inject隻注重“源頭”和“終點”,主要用于在高階元件庫中使用,在平常的開發中一般不适用的原因是不友善追溯“源頭”,不知道是哪一層聲明的和使用了。
子孫層中的provide會覆寫祖父層中相同key的屬性值。
provide 選項應該是一個對象或傳回一個對象的函數。該對象包含可注入其子孫的 property。
inject選項應該是一個字元串數組或一個對象,from是選自祖父元件provide的哪個值的key,default指定一個預設值。
// 父元件
provide: ["message"],
// 子元件
inject: {
msg: {
from: "message",
default: "default value"
}
}
複制代碼
// 父元件CompFather
<template>
<div>
<comp-son />
</div>
</template>
<script>
import CompSon from "./CompSon";
export default {
name: "CompFather",
components: {CompSon},
provide: {
msg: "父元件msg"
}
}
</script>
複制代碼
// 子元件 CompSon
<template>
<div>
子元件
</div>
</template>
<script>
export default {
name: "CompSon",
inject: ["msg"],
mounted() {
console.log(this.msg); // 父元件msg
}
}
</script>
複制代碼
provide 和 inject 綁定并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的對象,那麼其對象的 property 還是可響應的。例如provide和inject的是一個對象時,該資料是可響應的。
// 當provide/inject的是一個對象時,是可響應的,子元件改變值,父元件也會改變,父元件改變子元件也會改變
provide() {
return {
msg: this.msg
}
},
data() {
return {
msg: {
title: "父元件msg"
}
}
}
複制代碼
适用場景:适用于封裝高階元件,祖先執行個體不關心哪個後代執行個體會用到,後代執行個體不關心資料來源于哪個祖先執行個體。
非父子元件間的通信
中央事件總線 bus
使用中央事件總線實際就是建立一個vue執行個體,利用這個vue執行個體來傳遞消息。
使用方式一:
// 定義一個bus檔案
import Vue from "vue";
const bus = new Vue();
export default bus;
複制代碼
// 引入bus檔案
import bus from "@/bus";
bus.$emit("myEvent", "bus msg")
複制代碼
// 引入bus檔案
import bus from "@/bus";
bus.$on("myEvent", data => {
console.log(data);
});
複制代碼
使用方式二:
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.prototype.$bus = new Vue();
new Vue({
render: h => h(App),
}).$mount('#app')
複制代碼
// 發送事件
this.$bus.$emit("myEvent", "bus msg");
// 接收事件
this.$bus.$on("myEvent", data => {
console.log(data);
})
複制代碼
适用場景:适用于不是特别大的單頁面應用跨級跨兄弟元件間通信。
VueX
當我們在開發大型但單頁應用時,元件間需要頻繁使用修改某些值,這時候使用元件間傳參就顯得非常繁雜混亂,且不利于管理維護,vue為解決這一問題提供了VueX這個狀态管理工具,我們隻需要将多個元件需要共享的狀态放VueX中進行統一管理,形成一個全局單例管理模式,即可實作元件資料同步。
VueX的将要統一管理的狀态放在state中,這樣在引用了VueX的頁面中就能擷取到狀态,actions主要是處理例如背景請求的一些異步操作,保證資料的同步,通過dispatch方法調用actions中的方法,當得到異步的資料之後再通過commit調用mutations中的方法改變state。不要在元件中通過$store.state直接修改state資料,這樣Devtools是監控不到的,要經過mutations才可以。
這裡隻是簡要介紹通信方式,詳細的使用方法參見
[VueX官網] vuex.vuejs.org/zh/
總結
通信方式 | 适用場景 |
props/$emit | 直接父子元件傳值 |
$attrs/$listeners | $attrs非直接父子元件間傳值,$listeners監聽原生事件 |
$children/$parent | 一般用其它方式代替,不友善維護 |
可用于調用高階元件方法,如注冊element-ui元件引用 | |
封裝需要雙向綁定的元件時用v-modal進行傳參 | |
sync修飾符 | 用于父子元件間,子元件需要改變父元件值時 |
多用于高階元件庫,平常開發一般不使用,不利于代碼維護 | |
中央事件總線 | 适用于跨級或兄弟元件間通信。 |
用于大型單頁面中多個元件需要共享同一狀态時。 |