1. 前言
Vue
的一個核心思想就是組件化。所謂組件化,就是把頁面拆分成多個組件 (component
),每個組件依賴的 CSS
、JavaScript
、模板、圖片等資源放在一起開發和維護。組件是資源獨立的,組件在系統內部可復用,組件和組件之間可以嵌套。
我們在用 Vue
開發實際項目的時候,就是像搭積木一樣,編寫一堆組件拼裝生成頁面。那么組件之間必然少不了相互通信,而Vue
也提供了組件間通信的多種方式,本篇文章就來盤點一下在Vue
中組件間通信方式到底有幾種?
2. props / $emit(常用)
props
和$emit
適用於父子組件間通信,父組件通過props
的方式向子組件傳遞數據,而子組件可以通過$emit
向父組件通信。
2.1 父組件用props向子組件傳遞數據
下面通過一個例子說明父組件如何使用props
向子組件傳遞數據:
//父組件
<template>
<div>
<h1>父組件</h1>
<Son :brandList="brandList"></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
data() {
return {
brandList: ['BMW', 'Benz', 'Audi']
}
}
}
</script>
// 子組件
<template>
<div>
<h1>子組件</h1>
<span v-for="(item, index) in brandList" :key="index">{{item}}</span>
</div>
</template>
<script>
export default {
name: 'Son',
props: ['brandList']
}
</script>
示例說明:
父組件在調用子組件Son
時,需要把brandList
傳遞給子組件,那么就在調用子組件時把需要傳遞的數據添加到子組件的調用標簽上,如<Son :brandList="brandList"></Son>
。
而在子組件中,用props
屬性來接收父組件傳來的數據brandList
,接收之后,子組件就可以在自己的模板中使用傳來的數據了。
這樣,就完成了父組件向子組件的數據傳遞。
2.2 子組件用$emit向父組件傳遞數據
下面通過一個例子說明子組件如何使用$emit
向父組件傳遞數據:
// 子組件
<template>
<div>
<h1>子組件</h1>
<span v-for="(item, index) in brandList" :key="index" @click="emitBrand(item)"> {{item}}
</span>
</div>
</template>
<script>
export default {
name: 'Son',
props: ['brandList'],
methods: {
emitBrand(item){
this.$emit('onEmitBrand',item)
}
}
}
</script>
//父組件
<template>
<div>
<h1>父組件</h1>
<Son :brandList="brandList" @onEmitBrand="onEmitBrand"></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
data() {
return {
brandList: ['BMW', 'Benz', 'Audi']
}
},
methods: {
onEmitBrand(item) {
console.log(`您選擇的品牌是${item}`)
}
}
}
</script>
示例說明:
在子組件中,綁定了點擊事件emitBrand
,在這個點擊事件中使用$emit
廣播了一個名字為onEmitBrand
的消息,同時為該消息傳遞item
參數。
在父組件中,調用子組件的同時,也監聽了onEmitBrand
消息,一旦收到onEmitBrand
消息,就會執行對應的回調onEmitBrand
函數,在該回調函數中可以接收到子組件廣播消息時傳遞的item
參數。
捋一下流程就是:子組件觸發點擊事件emitBrand
后,此時廣播onEmitBrand
消息,同時攜帶數據item
,而此時監聽onEmitBrand
消息的父組件就會出發回調onEmitBrand
函數,同時獲得攜帶的數據item
。
這樣,就完成了子組件向父組件的數據傳遞。
3. $parent / children
在父子組件中,使用$parent
可以在子組件中獲取父組件的實例,而使用$children
可以在父組件中獲取所有子組件實例組成的數組。既然能夠拿到組件實例,就表明可以訪問到此組件的所有東西。
下面通過一個例子來說明:
//父組件
<template>
<div>
<h1>父組件</h1>
<div>父組件值:{{msg}}</div>
<p>獲取子組件值:{{this.$children[0].message}}</p>
<button @click="changSon">點擊改變子組件值</button>
<hr/>
<h1>子組件</h1>
<Son></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
data() {
return {
msg: 'hello world'
}
},
methods: {
changSon() {
this.$children[0].message = '父組件改變了子組件的值'
}
}
}
</script>
// 子組件
<template>
<div>
<h1>子組件</h1>
<div>子組件值:{{message}}</div>
<p>獲取父組件值:{{this.$parent.msg}}</p>
<button @click="changeParent">點擊改變父組件中的值</button>
</div>
</template>
<script>
export default {
name: 'Son',
data () {
return {
message: '這是子組件'
};
},
methods: {
changeParent(){
this.$parent.msg = '子組件改變了父組件的值'
}
}
}
</script>
示例說明:
在父組件中通過this.$children
可以獲取所有子組件實例組成的數組,然后可以通過數組下標的形式取出所需要的子組件實例,然后就可以訪問或修改子組件的內容了。
同樣,在子組件中通過this.$parent
可以獲取父組件的實例,然后就可以訪問或修改子組件的內容了。
值得注意的邊界情況是:
當獲取$parent
得到的是new Vue()
根實例時,如果在根實例上再獲取$parent
將得到的是undefined
,同樣,在最底層的子組件中獲取$children
將得到的是個空數組。
3.1 擴展內容
如果我們想在子組件中與祖父組件,甚至更上層的組件通信時,此時就會出現this.$parent.$parent.$parent....
,同樣,當在上層組件中想與孫子組件,甚至更下層組件通信時,也會出現this.$children.$children.$children....
,那么為了避免出現這種不優雅的情況,我們為此封裝兩個方法:$dispatch
和$broadcast
。
-
$dispatch——向上派發
Vue.prototype.$dispatch = function $dispatch(eventName, data) { let parent = this.$parent; while (parent) { parent.$emit(eventName, data); parent = parent.$parent; } };
如果子組件想向它的上層父級組件傳遞數據,我們就通過遞歸獲取
this.$parent
的方式,一層一層向上廣播消息以及攜帶需要傳遞的數據,然后在目的組件上監聽這個消息即可。 -
$broadcast——向下廣播
Vue.prototype.$broadcast = function $broadcast(eventName, data) { const broadcast = function () { this.$children.forEach((child) => { child.$emit(eventName, data); if (child.$children) { $broadcast.call(child, eventName, data); } }); }; broadcast.call(this, eventName, data); };
同理,如果父組件想向它的下層子級組件傳遞數據,我們就通過獲取
this.$children
的方式,一層一層向下廣播消息以及攜帶需要傳遞的數據,然后在目的組件上監聽這個消息即可。
4. provide / inject
provide
和 inject
這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深。簡單來說就是父組件中通過provide
來提供數據, 然后在子組件中通過inject
來接收提供的數據,同時不論子組件嵌套的有多深,只要父級組件通過provide
提供了數據,那么子組件就能夠通過inject
來接收到。
下面通過一個例子來說明:
//父組件
<template>
<div>
<h1>父組件</h1>
<Son></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
provide: {
NLRX: "難涼熱血"
}
}
</script>
// 子組件
<template>
<div>
<h1>子組件</h1>
{{NLRX}}
<Grandson></Grandson>
</div>
</template>
<script>
import Grandson from './Grandson.vue'
export default {
name: 'Son',
components: { Grandson },
inject: ['NLRX']
}
</script>
// 孫子組件
<template>
<div>
<h1>孫子組件</h1>
{{NLRX}}
</div>
</template>
<script>
export default {
name: 'Grandson',
inject: ['NLRX']
}
</script>
示例說明:
上面示例中展示了兩級嵌套的三個組件:父組件——>子組件——>孫子組件。在父組件中使用provide
提供了NLRX:'難涼熱血'
變量后,在子組件和孫子組件中均能夠使用inject
接收到。
5. $attrs / listeners
在vue2.4中引入了$attrs
和$listeners
,同時新增了inheritAttrs
選項,默認為true
。這兩個api
也可以用於父子組件間的通信,其中$attrs
向下傳遞屬性,$listeners
向下傳遞方法。
5.1 $attrs向下傳遞屬性
$attrs
包含了父作用域中不作為 prop
被識別 (且獲取) 的特性綁定 (class
和 style
除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class
和 style
除外),並且可以通過 v-bind="$attrs"
傳入內部組件。
下面通過一個例子來說明:
// 父組件
<template>
<div>
<h1>父組件</h1>
<Son
name="難涼熱血"
age="18"
gender="男"
height="175"
motto="叩首問路,碼夢為生!"
></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
}
</script>
// 子組件
<template>
<div>
<h1>子組件</h1>
<!-- 可以通過v-bind="$attrs"將屬性繼續向下傳遞 -->
<Grandson v-bind="$attrs"></Grandson>
</div>
</template>
<script>
import Grandson from './Grandson.vue'
export default {
name: 'Son',
components: { Grandson },
props: {
name: String // name作為props屬性綁定
},
inheritAttrs: false, // 此選項為false,組件根元素上的沒有在props聲明的屬性可以被$attrs獲取到,
// 若為true,則沒有在props聲明的屬性將會“回退”且作為普通的 HTML 特性應用在子組件的根元素上
created() {
console.log(this.$attrs);// { "age": "18", "gender": "男", "height": "175", "motto": "叩首問路,碼夢為生!" }
}
}
</script>
// 孫子組件
<template>
<div>
<h1>孫子組件</h1>
</div>
</template>
<script>
export default {
name: 'Grandson',
props: {
age: String
},
created() {
console.log(this.$attrs); // { "name": "難涼熱血", "gender": "男", "height": "175", "motto":"叩首問路,碼夢為生!" }
}
}
</script>
示例說明:
當父組件調用子組件時,為子組件傳遞了除props
選項接收的name
屬性之外的其他屬性,如age
、height
等,而這些多余的屬性在子組件中可以通過this.$attrs
獲取到,同時在子組件調用孫子組件時,通過為孫子組件添加v-bind="$attrs"
,可以將多余的屬性繼續向下傳遞。以達到父組件向子組件通信的目的。
5.2 $listeners向下傳遞方法
$listeners
包含了父作用域中的 (不含 .native
修飾器的) v-on
事件監聽器。它可以通過 v-on="$listeners"
傳入內部組件。
下面通過一個例子來說明:
// 父組件
<template>
<div>
<h1>父組件</h1>
<Son @click="()=>{consoloe.log('難涼熱血')}"></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
}
</script>
// 子組件
<template>
<div>
<h1>子組件</h1>
<!-- 可以通過v-on="$listeners"將方法繼續向下傳遞 -->
<Grandson v-on="$listeners"></Grandson>
</div>
</template>
<script>
import Grandson from './Grandson.vue'
export default {
name: 'Son',
components: { Grandson },
created() {
this.listeners.click() // 難涼熱血
}
}
</script>
// 孫子組件
<template>
<div>
<h1>孫子組件</h1>
</div>
</template>
<script>
export default {
name: 'Grandson',
created() {
this.listeners.click() // 難涼熱血
}
}
</script>
示例說明:
與$attrs
不同的是,$listeners
是傳遞方法,將父組件的方法向下傳遞。
6. ref / $refs
ref
被用來給元素或子組件注冊引用信息。引用信息將會注冊在父組件的 $refs
對象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例。
下面通過一個例子來說明:
// 父組件
<template>
<div>
<h1>父組件</h1>
<Son ref='son'></Son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Parent',
components: { Son },
methods: {
a () {
const son = this.$refs.son;
console.log(son.name); // Son
console.log(son.message); // 這是子組件
son.sayHello(); // hello
}
}
}
</script>
// 子組件
<template>
<div>
<h1>子組件</h1>
</div>
</template>
<script>
export default {
name: 'Son',
data () {
return {
message: '這是子組件'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
</script>
示例說明:
父組件在調用子組件時,為子組件添加了ref
屬性,那么在父組件中就可以通過this.$refs.son
來獲取到子組件的實例,這樣就可以訪問子組件上的東西了。
7. eventBus(常用)
eventBus
又稱為事件總線,在vue
中可以用它來作為組件之間通信的橋梁, 所有組件共用相同的事件中心,所有組件都用它來注冊發送事件和接收事件,其實這就是一個典型的發布訂閱模式,由各個組件向eventBus
訂閱事件,並由eventBus
發布事件,對於小型不復雜的項目可以使用這種方式。
它的使用方式如下:
-
創建一個事件總線並將其導出
// event-bus.js import Vue from 'vue' export const EventBus = new Vue()
-
發布事件
// A組件 <template> <div> <h1>A組件</h1> <button @click="emitEvent">發布事件</button> </div> </template> <script> import {EventBus} from './event-bus.js' export default { name: 'ComA', methods: { emitEvent () { EventBus.$emit('nlrx', { nlrx:'難涼熱血' }) } } } </script>
創建一個A組件,並且在A組件中發布一個名為
nlrx
的消息,消息內容是{nlrx:'難涼熱血'}
。 -
訂閱事件
// B組件 <template> <div> <h1>B組件</h1> <button @click="receiveEvent">接收事件</button> </div> </template> <script> import {EventBus} from './event-bus.js' export default { name: 'ComB', mounted() { EventBus.$on('nlrx', data => { console.log(data.nlrx) // 難涼熱血 }) } } </script>
創建一個B組件,並且在B組件中訂閱監聽
nlrx
消息,同時回調函數中的data
參數即是消息內容{nlrx:'難涼熱血'}
。
8. vuex(常用)
Vuex
是一個專為 Vue
應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
Vuex
解決了多個視圖依賴於同一狀態和來自不同視圖的行為需要變更同一狀態的問題,將開發者的精力聚焦於數據的更新而不是數據在組件之間的傳遞上。
關於Vuex
的具體介紹以及使用方式可參見之前的一篇博文:通俗易懂了解Vuex!
9. 總結
以上就是盤點了Vue
中組件間通信的七種方式,這些方式根據使用場景大致可分為:
- 父子組件通信:
props / $emit
;$children / $parent
;provide / inject
;ref / refs
;$attrs / $listeners
- 兄弟組件通信:
eventBus
;vuex
- 跨級組件通信:
eventBus
;Vuex
;provide / inject
(完)