組件通信
父子組件通信
父傳子 props屬性
子傳父 $emit事件
這兩種官方文檔里有很詳細(xì)的介紹就不解釋了
還是舉個栗子:
//parent.vue父組件
<template>
<div>
parent: {{money}}
<Son1 v-model="money"></Son1>
<!-- <Son1 :value.sync = "money"></Son1> -->
//兩種寫法都可以,是語法躺;.sync表示同步,如果只傳value 用v-model即可
</div>
</template>
<script>
import Son1 from './Son1'
export default {
components:{
Son1
},
data(){
return{
money:100
}
}
}
</script>
//子組件SON
//單項數(shù)據(jù)流,父給子綁定一個事件
<template>
<div>
son:{{value}}
<button @click="change">點擊</button>
</div>
</template>
<script>
export default {
methods:{
change(){
this.$emit('input', this.value+100)
}
},
props:{
value:{
type:Number,
default:1
}
},
data(){
return{
}
}
}
</script>
多層級傳遞數(shù)據(jù)
$dispatch 和 $broadcast (Vue1中可以用來實現(xiàn)基于組件樹結(jié)構(gòu)的事件流通信,vue2中已經(jīng)被移除)
(父要傳到孫;孫要傳到父;)
dispatch 是一個事件,首先會在自己實例本身上觸發(fā),然后沿父鏈向上傳播。
broadcast 是一個事件,它向下傳播到當(dāng)前實例的所有后代。由于后代擴(kuò)展為多個子樹,事件傳播將會遵循許多不同的“路徑”。 除非回調(diào)返回 true,否則在沿該路徑觸發(fā)偵聽器回調(diào)時,每個路徑的傳播將會停止。
//grandoon.vue組件
<template>
<div>
grandson:{{value}}
<button @click="changeParent">修改parent</button>
</div>
</template>
<script>
export default {
methods:{
changeParent(){
// this.$parent.$emit('input',300)
// this.$parent.$parent.$emit('input',300)
this.$dispatch('input',300)
}
},
props:{
value:{
type:Number
}
}
}
</script>
//dispatch實現(xiàn)
/**
* Recursively propagate an event up the parent chain.
* 遞歸地在父鏈上傳播事件。
* @param {String} event
* @param {...*} additional arguments
*/
// $dispatch 方法是定義在 Vue 的 prototype 上的
// 接受一個字符串類型的事件名稱
Vue.prototype.$dispatch = function (event) {
// 首先執(zhí)行 $emit 觸發(fā)事件,將返回值保存在 shouldPropagate 中
var shouldPropagate = this.$emit.apply(this, arguments)
// 如果首次執(zhí)行的 $emit 方法返回的值不是 true 就直接返回
// 如果返回值不是 true 就說明組件邏輯不希望事件繼續(xù)往父組件進(jìn)行傳遞
if (!shouldPropagate) return
// 如果首次執(zhí)行 $emit 方法返回值是 true 就獲取當(dāng)前組件的 parent 組件實例
var parent = this.$parent
// 將函數(shù)接受的參數(shù)轉(zhuǎn)換成數(shù)組
var args = toArray(arguments)
// use object event to indicate non-source emit on parents
// 根據(jù)傳入的事件名稱的參數(shù)組裝成 object
args[0] = { name: event, source: this }
// 循環(huán)知道組件的父組件
while (parent) {
// 在父組件中執(zhí)行 $emit 觸發(fā)事件
shouldPropagate = parent.$emit.apply(parent, args)
// 如果父組件 $emit 返回的是 true 就繼續(xù)遞歸祖父組件,否則就停止循環(huán)
parent = shouldPropagate ? parent.$parent : null
}
// 最后返回當(dāng)前組件實例
return this
}
broadcast 實現(xiàn)
Vue.prototype.$broadcast = function (event) {
// 獲取傳入事件的類型,判斷是否為字符串
var isSource = typeof event === 'string'
// 校正 event 的值,當(dāng)接受 event 的類型為字符串時就直接使用,如果不是字符串就使用 event 上的 name 屬性
event = isSource ? event : event.name
// if no child has registered for this event,
// then there's no need to broadcast.
// 如果當(dāng)前組件的子組件沒有注冊該事件,就直接返回,并不用 broadcast
if (!this._eventsCount[event]) return
// 獲取當(dāng)前組件的子組件
var children = this.$children
// 將函數(shù)接受的參數(shù)轉(zhuǎn)換成數(shù)組
var args = toArray(arguments)
// 如果傳入事件為字符串
if (isSource) {
// use object event to indicate non-source emit
// on children
// 根據(jù)傳入的事件名稱的參數(shù)組裝成 object
args[0] = { name: event, source: this }
}
組件傳值,尤其是祖孫組件有跨度的傳值。
現(xiàn)在我們來討論一種情況,A組件與C組件怎么通信,我們有多少種解決方案?
- 我們使用VueX來進(jìn)行數(shù)據(jù)管理,但是如果項目中多個組件共享狀態(tài)比較少,項目比較小,并且全局狀態(tài)比較少,那使用VueX來實現(xiàn)該功能,并沒有發(fā)揮出VueX的威力。
- 使用B來做中轉(zhuǎn)站,當(dāng)A組件需要把信息傳給C組件時,B接受A組件的信息,然后利用屬性傳給C組件,這是一種解決方案,但是如果嵌套的組件過多,會導(dǎo)致代碼繁瑣,代碼維護(hù)比較困難;如果C中狀態(tài)的改變需要傳遞給A, 使用事件系統(tǒng)一級級往上傳遞 。
- 自定義一個Vue 中央數(shù)據(jù)總線,這個情況適合碰到組件跨級傳遞消息,但是使用VueX感覺又有點浪費的項目中,但是缺點是,碰到多人合作時,代碼的維護(hù)性較低,代碼可讀性低
listeners
在vue2.4中,為了解決該需求,引入了listeners , 新增了inheritAttrs 選項。
$attrs (屬性集合)
$listeners (方法集合)
v-bind="$attrs", v-on="$listeners"
$attrs包含了父作用域中不作為 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)
provide inject
1.provide就相當(dāng)于加強(qiáng)版父組件prop
2.inject就相當(dāng)于加強(qiáng)版子組件的props
因為以上兩者可以在父組件與子組件、孫子組件、曾孫子...組件數(shù)據(jù)交互,也就是說不僅限于prop的父子組件數(shù)據(jù)交互,只要在上一層級的聲明的provide,那么下一層級無論多深都能夠通過inject來訪問到provide的數(shù)據(jù)
//父組件
<template>
<div class="test">
<son prop="data"></son>
</div>
</template>
<script>
export default {
name: 'Test',
provide: {
name: 'Garrett'
}
//孫組件 (父組件--子組件--根組件)
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: 'Grandson',
inject: [name]
}
</script>
缺點:
這么做也是有明顯的缺點的,在任意層級都能訪問導(dǎo)致數(shù)據(jù)追蹤比較困難,不知道是哪一個層級聲明了這個或者不知道哪一層級或若干個層級使用了,因此這個屬性通常并不建議使用能用vuex的使用vuex,不能用的多傳參幾層,但是在做組件庫開發(fā)時,不對vuex進(jìn)行依賴,且不知道用戶使用環(huán)境的情況下可以很好的使用
event bus
實現(xiàn)途徑是在要相互通信的兄弟組件之中,都引入一個新的vue實例,然后通過分別調(diào)用這個實例的事件觸發(fā)和監(jiān)聽來實現(xiàn)通信和參數(shù)傳遞。
有了eventbus后
發(fā)送組件中
EventBus.$emit("hello", this.number);
接受組件中
EventBus.$on("hello", (number) = > { console.log(number) });
注意
- $bus.on應(yīng)該在created鉤子內(nèi)使用,如果在mounted使用,有可能接收不到其他組件來自created鉤子內(nèi)發(fā)出的事件。
- 使用了
bus.off解除,因為組件銷毀后,沒有必要把監(jiān)聽句柄存儲在vue-bus里了。