組件作為Vue中的核心概念,是值得我們深入研究的課題之一,通過研究它,我們可以理解更高深的思想,可以提升自己的開發(fā)技巧。而今天,我要討論的是Vue的組件通信。
眾所周知,組件通信是通過props和emit去完成的,但實際上,這只是眾多方式中的一種而已。而且針對不同的情況,會有更合適的方法。下面就聽我慢慢道來。
1.props和emit
父組件:
<template>
<div class="parent-box">
<h3>我是父元素,props方式</h3>
<p class="content">
通過$emit獲得子元素屬性{{children2}}
</p>
<Children2 @changeChild2="changeChild2"></Children2>
</div>
</template>
<script>
data(){
return {
children2:'children2',
}
},
methods:{
changeChild2(val){
this.children2 = val
},
}
</script>
子組件
<template>
<div class="children-box">
<h4>我是子元素</h4>
<button @click="clickEvent">改值</button>
<p>通過props通信 {{value}}</p>
</div>
</template>
<script>
export default {
props:['value'],
methods:{
clickEvent(){
// 核心代碼
this.$emit('changeChild2',Math.random())
}
}
}
</script>
大體效果如下:
可以看到,通過點擊按鈕,可以改變通過props傳入子組件的value屬性。因為這種方式是大家最常用的一種方式,這里就不做詳細解釋了。
2.$parent
和 $children
$parent
屬性可以用來從一個子組件訪問父組件的實例。它提供了一種機會,可以在后期隨時觸達父級組件,以替代將數據以 prop 的方式傳入子組件的方式。
$children
可以訪問當前實例的直接子組件。
下面來看一個例子,代碼如下,注意注釋部分。
父組件
<template>
<div class="parent-box">
<h3>我是父元素,$parent,$children方式</h3>
<p class="content">
通過$children獲得子元素屬性{{children1}}
</p>
<Children1></Children1>
</div>
</template>
<script>
data(){
return {
parent1:'parent1',
children1:'children1',
}
},
mounted() {
// 核心代碼,通過$children獲取子組件的屬性
this.children1 = this.$children[0]._data.value
},
</script>
子組件
<template>
<div class="children-box">
<h4>我是子元素</h4>
<input type="text" v-model="parent">
<p>通過$parent獲取父元素的屬性 {{parent}}</p>
</div>
</template>
<script>
export default {
data(){
return {
// 核心代碼,通過$parent獲得父組件的屬性
parent:this.$parent._data.parent1
}
},
mounted() {
},
watch:{
parent(val){
// 核心代碼,改變父組件中的屬性
this.$parent._data.parent1 = val
}
}
}
</script>
大體效果如下:
可以看到,通過this.$parent._data.parent1 = val,改變子組件中parent的值,然后賦值給父組件的parent1可以直接改變父組件的屬性值。
雖然這種方式比較方便快捷,但有很大的副作用,就如官網所說:
在絕大多數情況下,觸達父級組件會使得你的應用更難調試和理解,尤其是當你變更了父級組件的數據的時候。當我們稍后回看那個組件的時候,很難找出那個變更是從哪里發(fā)起的。
節(jié)制地使用
$parent
和$children
- 它們的主要目的是作為訪問組件的應急方法。更推薦用 props 和 events 實現父子組件通信
3.總線方式
有時候,我們的組件并不止父子關系這么簡單,可能兄弟組件之間也要進行通信,而EventBus就能解決這個問題,相對于vuex它更輕量,不需要我們引入vuex這個龐然大物,更加適合小型項目。
我們在主實例App之外,單獨定義一個空的Bus實例,來進行組件間的通信。
下面來看一個例子,代碼如下
bus.js
// 核心代碼
import Vue from 'vue'
var Bus = new Vue()
export default Bus
父組件:
<template>
<div class="parent-box">
<h3>我是父元素,總線方式</h3>
<Children31></Children31>
<Children32></Children32>
</div>
</template>
子組件1
<template>
<div class="children-box">
<h4>我是子元素</h4>
<p>通過總線方式通信 {{msg}}</p>
</div>
</template>
<script>
import Bus from '../bus'
export default {
data(){
return {
msg:'hello world'
}
},
mounted() {
},
created(){
// 核心代碼,接受事件
Bus.$on('setMsg',val=>{
this.msg = val
})
},
}
</script>
子組件2
<template>
<div class="children-box">
<h4>我是子元素</h4>
<input type="text" v-model="msg">
<p>通過總線方式通信 {{msg}}</p>
</div>
</template>
<script>
import Bus from '../bus'
export default {
data(){
return {
msg:'hello world'
}
},
mounted() {
},
watch:{
msg:function (newVal) {
// 核心代碼發(fā)出事件
Bus.$emit('setMsg',newVal)
},
}
}
</script>
大體效果如下:
可以看到,我改變子組件2 input的值會觸發(fā)emit事件,去改變子組件1中的msg。
4.$attrs
和$listeners
$attrs
和$listeners
是2.4.0才新加入的方法,用來解決組件的跨級傳輸非常有用。試想有A、B、C三個組件,A包含B,B包含C,如果我想在A上給C傳參,并且接收C的事件怎么辦呢?
原先,我們只使用pros去傳參的話,就只能拿B作為中轉組件,B組件定義足夠多的pros,不僅僅用于自身,還要用于傳輸給C,而事件的傳遞,也只能一層層地往上傳,這樣就會使代碼很繁瑣,臃腫,不利于維護。
而$attrs
和$listeners
就是用來處理這種情況的,代碼如下
父組件A
<template>
<div class="parent-box">
<h3>我是父元素A,$attrs,$listeners方式</h3>
<Children4 :value1="value1" :value2="value2" @clickEvent1="clickEvent1" @clickEvent2="clickEvent2"></Children4>
</div>
</template>
<script>
data(){
value1:'B',
value2:'C',
},
methods:{
clickEvent1(){
this.value1 = Math.random()
},
clickEvent2(){
this.value2 = Math.random()
}
}
</script>
子組件B
<template>
<div class="children-box">
<h4>我是子元素B</h4>
<p>{{value1}}</p>
<button @click="clickEvent">改變value1的值</button>
<Children42 v-bind="$attrs" v-on="$listeners"></Children42>
</div>
</template>
<script>
import Children42 from './Children4.2'
export default {
name:'Children41',
inheritAttrs:false,
props:['value1'],
components:{
Children42
},
methods:{
clickEvent(){
this.$emit('clickEvent1')
}
},
}
</script>
子組件C
<template>
<div class="children-box">
<h4>我是子元素C</h4>
<p>{{value2}}</p>
<button @click="clickEvent">改變value1的值</button>
</div>
</template>
<script>
export default {
name:'Children42',
inheritAttrs:false,
props:['value2'],
data(){
return {
}
},
mounted() {
},
methods:{
clickEvent(){
this.$emit('clickEvent2')
}
},
}
</script>
大體效果如下:
可以看到,我們只要在引用C組件的時候,加入v-bind="$attrs
" v-on="$listeners
"兩個屬性即可,這樣,C組件就可以接收到來自A組件的值,A組件也能接收到來自C組件的事件。
如此以來,就不需要在B組件定義中轉的屬性和方法,如果你的組件結構比較復雜,這種方式可以很大程度減少代碼的冗余,更加的輕量化。
5.provide和inject
這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關系成立的時間里始終生效。如果你熟悉 React,這與 React 的上下文特性很相似。
這相對于attrs和listeners可能更加簡介,只需要父組件提供變量,子組件注入就行,不需要在中間組件寫什么代碼,但并不推薦在業(yè)務代碼中使用,正如官方所說。
provide 和 inject 主要為高階插件/組件庫提供用例。并不推薦直接用于應用程序代碼中。
因為provide inject 會有一個類似冒泡的特性,數據源有可能在中間被”“打斷”,甚至是有可能被組件庫中的組件打斷,或者打斷組件庫中的provide,不利于維護
代碼如下:
父組件
<template>
<div class="parent-box">
<h3>我是父元素,provide,inject方式</h3>
<Children5></Children5>
</div>
</template>
<script>
data(){
return {
theme:'blue'
},
// 核心代碼
provide(){
return {
test:this
}
},
}
</script>
子組件
<template>
<div class="children-box">
<h4 :style="{color:value.theme}">我是子元素</h4>
<div @click="changeValue">改顏色</div>
</div>
</template>
<script>
export default {
// 核心代碼
inject: {
value:{
from:'test',
default:()=>{}
}
},
methods:{
changeValue(){
this.value.theme = 'red'
}
}
}
</script>
大體效果如下:
我們在父組件中提供一個test屬性,然后賦值為this,這里之所以賦值this,是為了讓provide和inject的綁定變成可響應的,這樣,我再子組件中就可以直接改變父組件的theme屬性。
6. Vuex
Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應用的所有組件的狀態(tài),并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。
Vuex的功能強大,但應對簡單的組件通信用Vuex就顯得多余了,有種殺雞用牛刀的感覺,還會增加我們代碼的理解難度。
Vuex作為我們必須掌握的技能之一,這里也不再贅述,不了解的話,官網就是最好的學習材料。
總結
vue中組件通信的方式很多,應對不同情況,靈活地采用最適合的方式,才能使我們的代碼變得優(yōu)雅。
以上是目前為止,我所知的所有通信方式,如有遺漏,歡迎補充。
以下,是代碼的demo地址
https://github.com/hanwolfxue/blog-demo-vue-communicate.git