vue組件通信方式全面詳解
眾所周知,Vue主要思想就是組件化開發。因為,在實際的項目開發中,肯定會以組件的開發模式進行。形如頁面和頁面之間需要通信一樣,Vue 組件和組件之間肯定也需要互通有無、共享狀態。接下來,我們就悉數給大家展示所有 Vue 組件之間的通信方式。
組件關系
- App組件和A組件、A組件和B組件、B組件和C組件形成父子關系
- B組件和D組件形成兄弟關系
- App組件和C組件、App和B組件形成了隔代關系(其中的層級可能是多級,既隔多代)
組件通信
這么多的組件關系,那么組件和組件之間又有哪些通信的方式呢?各種方式的區別又是什么?適用場景又是什么呢?
props和$emit
這種方式是我們日常開發中應用最多的一種方式。
props以單向數據流的形式可以很好的完成父子組件的通信
所謂單向數據流:就是數據只能通過 props 由父組件流向子組件,而子組件並不能通過修改 props 傳過來的數據修改父組件的相應狀態。至於為什么這樣做,Vue 官網做出了解釋:
**所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。
額外的,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新為最新的值。這意味着你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制台中發出警告。
正因為這個特性,於是就有了對應的 $emit
。$emit
用來觸發當前實例上的事件。對此,我們可以在父組件自定義一個處理接受變化狀態的邏輯,然后在子組件中如若相關的狀態改變時,就觸發父組件的邏輯處理事件。
let Child = {
template: `<div>
<input type="text" v-model='msg'/>
<button @click='handleClick'>傳遞</button>
</div>`,
props: ['msg'],
methods: {
handleClick() {
this.$emit('getChildData', '子組件數據')
}
},
}
let Parent = {
data() {
return {
msg: '小馬哥',
val:''
}
},
methods: {
getChildData(val) {
this.val = val;
}
},
template: `
<div>
<p>我是一個父組件</p>
<p>我是{{val}}</p>
<Child :msg='msg' @getChildData='getChildData'></Child>
</div>
`,
components: {
Child
}
}
let vm = new Vue({
el: '#app',
template: `
<div>
<Parent></Parent>
</div>
`,
components: {
Parent
}
})
- 父傳子:父組件傳遞msg數據給子組件,通過v-bind綁定msg,子組件中直接可以用props接收綁定的數據
- 子傳父:子組件觸發相應的事件,通過$emit觸發事件傳遞數據,父組件中綁定對應的事件,通過$on監聽對應的事件 接收子組件傳遞的數據
EventBus-中央事件總線
如果想實現兄弟組件之間進行通信,在項目規模不大的情況下,完全可以使用中央事件總線EventBus
的方式。如果你的項目規模是大中型的,那我們會使用vuex狀態管理
EventBus
通過新建一個Vue
事件bus
對象,通過bus.$emit
觸發事件,bus.$on監聽觸發的事件。
Vue.component('A', {
template: `
<div>
<p>我是A組件</p>
<button @click='handleClick'>A傳遞到B</button>
</div>
`,
data() {
return {
msg: 'hello 小馬哥'
}
},
methods: {
handleClick() {
this.$bus.$emit('globalEvent',this.msg);
}
},
})
Vue.component('B', {
template: `
<div>
<p>我是B組件</p>
<h3>{{aValue}}</h3>
</div>
`,
data() {
return {
aValue: ''
}
},
created () {
this.$bus.$on('globalEvent',(val)=>{
this.aValue = val;
})
},
})
// 定義中央事件總線
let bus = new Vue();
// 將中央事件總線賦值給Vue.prototype中,這樣所有組件都能訪問到了
Vue.prototype.$bus = bus;
let vm = new Vue({
el: '#app',
template: `
<div>
<A></A>
<B></B>
</div>
`,
})
$attrs和$listeners
通過 props
進行組件通信的方式只適合直接的父子組件,如果父組件A下面有子組件B,組件B下面有組件C,這時如果組件A直接想傳遞數據給組件C那就行不通了! 只能是組件A通過 props 將數據傳給組件B,然后組件B獲取到組件A 傳遞過來的數據后再通過 props 將數據傳給組件C。當然這種方式是非常復雜的,無關組件中的邏輯業務一種增多了,代碼維護也沒變得困難,再加上如果嵌套的層級越多邏輯也復雜,無關代碼越多!
針對這樣一個問題,Vue 2.4提供了$attrs
和$listeners
來實現能夠直接讓組件A傳遞消息給組件C
Vue.component('A', {
template: `
<div>
<p>我是A組件</p>
<B :msg='msg' @getCData='getCData'></B>
</div>
`,
methods: {
getCData(val) {
alert(val)
}
},
data() {
return {
msg: 'hello 小馬哥'
}
},
})
Vue.component('B', {
template: `
<div>
<p>我是B組件</p>
<!-- C組件中能直接觸發 getCData 的原因在於:B組件調用 C組件時,使用 v-on 綁定了 $listeners 屬性 -->
<!-- 通過v-bind 綁定 $attrs 屬性,C組件可以直接獲取到 A組件中傳遞下來的 props(除了 B組件中 props聲明的) -->
<C v-bind='$attrs' v-on='$listeners'></C>
</div>
`,
// props: ['msg'],
data() {
return {
}
}
})
Vue.component('C', {
template: `
<div>
<p>我是C組件</p>
<p>{{$attrs.msg}}</p>
<button @click='handleClick'>傳遞數據</button>
</div>
`,
methods: {
handleClick() {
this.$emit('getCData', 'C組件的數據')
}
},
data() {
return {
}
}
})
let vm = new Vue({
el: '#app',
template: `
<div>
<A></A>
</div>
`,
})
$attrs
:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class
和style
除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定屬性 (class和style
除外),並且可以通過v-bind="$attrs"
傳入內部組件。$listeners
:包含了父作用域中的 (不含.native
修飾器的)v-on
事件監聽器。它可以通過v-on="$listeners"
傳入內部組件。
provide和inject
在父組件中通過 provider
來提供屬性,然后在子組件中通過 inject 來注入變量。不論子組件有多深,只要調用了 inject
那么就可以注入在 provider 中提供的數據,而不是局限於只能從當前父組件的 prop 屬性來獲取數據,只要在父組件的生命周期內,子組件都可以調用。這和 React 中的 Context API
有沒有很相似!
Vue.component('A', {
template: `
<div>
<p>我是A組件</p>
<B></B>
</div>
`,
provide:{
a:"祖先A的數據"
},
data() {
return {
msg: 'hello 小馬哥'
}
},
})
Vue.component('B', {
template: `
<div>
<p>我是B組件</p>
<C></C>
</div>
`,
data() {
return {
}
}
})
Vue.component('C', {
template: `
<div>
<p>我是C組件</p>
<h3>{{a}}</h3>
</div>
`,
inject:['a'],
data() {
return {
}
}
})
let vm = new Vue({
el: '#app',
template: `
<div>
<A></A>
</div>
`,
})
- 在
parent
組件中,通過provide
屬性,以對象的形式向子孫組件暴露了一些屬性 - 在
child
組件中,通過inject
屬性注入了parent
組件提供的數據,實際這些通過inject
注入的屬性是掛載到 Vue 實例上的,所以在組件內部可以通過 this 來訪問
⚠️ 注意:官網文檔提及 provide 和 inject 主要為高階插件/組件庫提供用例,並不推薦直接用於應用程序代碼中。
$parent和$children
這里要說的這種方式就比較直觀了,直接操作父子組件的實例。$parent
就是父組件的實例對象,而 $children
就是當前實例的直接子組件實例了,不過這個屬性值是數組類型的,且並不保證順序,也不是響應式的。
Vue.component('Parent', {
template: `
<div>
<p>我是父組件</p>
{{msg}}
<hr/>
<Child></Child>
</div>
`,
mounted () {
//讀取子組件數據,注意$children並不保證順序,也不是響應式的
console.log(this.$children[0].a)
},
data() {
return {
msg: 'hello 小馬哥'
}
},
})
Vue.component('Child', {
template: `
<div>
<p>我是孩子組件</p>
<input type="text" @input='changeValue'/>
<h2>{{myMsg}}</h2>
</div>
`,
methods: {
changeValue() {
this.$parent.msg = '子組件中的數據'
}
},
data() {
return {
myMsg:this.$parent.msg,
a:"小馬哥"
}
}
})
let vm = new Vue({
el: '#app',
template: `
<div>
<Parent></Parent>
</div>
`,
})
Vuex狀態管理
Vuex 是狀態管理工具,實現了項目狀態的集中式管理。工具的實現借鑒了 Flux、Redux、和 The Elm Architecture 的模式和概念。當然與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。詳細的關於 Vuex 的介紹,你既可以去查看官網文檔,也可以查看本專欄關於 Vuex 一系列的介紹。