Vue組件間的通信方式詳析
前言
在Vue組件庫(kù)開(kāi)發(fā)過(guò)程中,Vue組件之間的通信一直是一個(gè)重要的話題,雖然官方推出的 Vuex 狀態(tài)管理方案可以很好的解決組件之間的通信問(wèn)題,但是在組件庫(kù)內(nèi)部使用 Vuex 往往會(huì)比較重,本文將系統(tǒng)的羅列出幾種不使用 Vuex,比較實(shí)用的組件間的通信方式,供大家參考。
組件之間通信的場(chǎng)景
在進(jìn)入我們今天的主題之前,我們先來(lái)總結(jié)下Vue組件之間通信的幾種場(chǎng)景,一般可以分為如下幾種場(chǎng)景:
- 父子組件之間的通信
- 兄弟組件之間的通信
- 隔代組件之間的通信
父子組件之間的通信
父子組件之間的通信應(yīng)該是 Vue 組件通信中最簡(jiǎn)單也最常見(jiàn)的一種了,概括為兩個(gè)部分:父組件通過(guò)prop向子組件傳遞數(shù)據(jù),子組件通過(guò)自定義事件向父組件傳遞數(shù)據(jù)。
父組件通過(guò) prop 向子組件傳遞數(shù)據(jù)
Vue組件的數(shù)據(jù)流向都遵循單向數(shù)據(jù)流的原則,所有的 prop 都使得其父子 prop 之間形成了一個(gè)單向下行綁定:父級(jí) prop 的更新會(huì)向下流動(dòng)到子組件中,但是反過(guò)來(lái)則不行。這樣會(huì)防止從子組件意外變更父級(jí)組件的狀態(tài),從而導(dǎo)致你的應(yīng)用的數(shù)據(jù)流向難以理解。
額外的,每次父級(jí)組件發(fā)生變更時(shí),子組件中所有的 prop 都將會(huì)刷新為最新的值。這意味著你不應(yīng)該在一個(gè)子組件內(nèi)部改變 prop。如果你這樣做了,Vue 會(huì)在瀏覽器的控制臺(tái)中發(fā)出警告。
父組件 ComponentA:
<template>
<div>
<component-b title="welcome"></component-b>
</div>
</template>
<script>
import ComponentB from './ComponentB'
export default {
name: 'ComponentA',
components: {
ComponentB
}
}
</script>子組件 ComponentB:
<template>
<div>
<div>{{title}}</div>
</div>
</template>
<script>
export default {
name: 'ComponentB',
props: {
title: {
type: String,
}
}
}
</script>子組件通過(guò)自定義事件向父組件傳遞數(shù)據(jù)
在子組件中可以通過(guò) $emit 向父組件發(fā)生一個(gè)事件,在父組件中通過(guò) v-on/@ 進(jìn)行監(jiān)聽(tīng)。
子組件 ComponentA:
<template>
<div>
<component-b :title="title" @title-change="titleChange"></component-b>
</div>
</template>
<script>
import ComponentB from './ComponentB'
export default {
name: 'ComponentA',
components: {
ComponentB
},
data: {
title: 'Click me'
},
methods: {
titleChange(newTitle) {
this.title = newTitle
}
}
}
</script>子組件 ComponentB:
<template>
<div>
<div @click="handleClick">{{title}}</div>
</div>
</template>
<script>
export default {
name: 'ComponentB',
props: {
title: {
type: String,
}
},
methods: {
handleClick() {
this.$emit('title-change', 'New title !')
}
}
}
</script>這個(gè)例子非常簡(jiǎn)單,在子組件 ComponentB 里面通過(guò) $emit 派發(fā)一個(gè)事件 title-change,在父組件 ComponentA 通過(guò) @title-change 綁定的 titleChange 事件進(jìn)行監(jiān)聽(tīng),ComponentB 向 ComponentA 傳遞的數(shù)據(jù)在 titleChange 函數(shù)的傳參中可以獲取到。
兄弟組件之間的通信
狀態(tài)提升
寫過(guò) React 的同學(xué)應(yīng)該對(duì)組件的 狀態(tài)提升 概念并不陌生,React 里面將組件按照職責(zé)的不同劃分為兩類:展示型組件(Presentational Component) 和 容器型組件(Container Component)。
展示型組件不關(guān)心組件使用的數(shù)據(jù)是如何獲取的,以及組件數(shù)據(jù)應(yīng)該如何修改,它只需要知道有了這些數(shù)據(jù)后,組件UI是什么樣子的即可。外部組件通過(guò) props 傳遞給展示型組件所需的數(shù)據(jù)和修改這些數(shù)據(jù)的回調(diào)函數(shù),展示型組件只是它們的使用者。
容器型組件的職責(zé)是獲取數(shù)據(jù)以及這些數(shù)據(jù)的處理邏輯,并把數(shù)據(jù)和邏輯通過(guò) props 提供給子組件使用。
因此,參考 React 組件中的 狀態(tài)提升 的概念,我們?cè)趦蓚€(gè)兄弟組件之上提供一個(gè)父組件,相當(dāng)于容器組件,負(fù)責(zé)處理數(shù)據(jù),兄弟組件通過(guò) props 接收參數(shù)以及回調(diào)函數(shù),相當(dāng)于展示組件,來(lái)解決兄弟組件之間的通信問(wèn)題。
ComponentA(兄弟組件A):
<template>
<div>
<div>{{title}}</div>
<div @click="changeTitle">click me</div>
</div>
</template>
<script>
export default {
name: 'ComponentA',
props: {
title: {
type: String
},
changeTitle: Function
}
}
</script>ComponentB(兄弟組件B):
<template>
<div>
<div>{{title}}</div>
<div @click="changeTitle">click me</div>
</div>
</template>
<script>
export default {
name: 'ComponentB',
props: {
title: {
type: String
},
changeTitle: Function
}
}
</script>ComponentC(容器組件C):
<template>
<div>
<component-a :title="titleA" :change-title="titleAChange"></component-a>
<component-b :title="titleB" :change-title="titleBChange"></component-b>
</div>
</template>
<script>
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
export default {
name: 'ComponentC',
components: {
ComponentA,
ComponentB
},
data: {
titleA: 'this is title A',
titleB: 'this is title B'
},
methods: {
titleAChange() {
this.titleA = 'change title A'
},
titleBChange() {
this.titleB = 'change title B'
}
}
}
</script>可以看到,上述這種 "狀態(tài)提升" 的方式是比較繁瑣的,特別是兄弟組件的通信還要借助于父組件,組件復(fù)雜之后處理起來(lái)是相當(dāng)麻煩的。
隔代組件之間的通信
隔代組件之間的通信可以通過(guò)如下幾種方式實(shí)現(xiàn):
$attrs/$listenersrovide/inject- 基于
$parent/$children實(shí)現(xiàn)的dispatch和broadcast
attrs/attrs/listeners
Vue 2.4.0 版本新增了 $attrs 和 $listeners 兩個(gè)方法。先看下官方對(duì) $attrs 的介紹:
包含了父作用域中不作為 prop 被識(shí)別 (且獲取) 的 attribute 綁定(
class和style除外)。當(dāng)一個(gè)組件沒(méi)有聲明任何 prop 時(shí),這里會(huì)包含所有父作用域的綁定 (class和style除外),并且可以通過(guò)v-bind="$attrs"傳入內(nèi)部組件——在創(chuàng)建高級(jí)別的組件時(shí)非常有用。
看個(gè)例子:
組件A(ComponentA):
<template>
<component-a name="Lin" age="24" sex="male"></component-a>
</template>
<script>
import ComponentB from '@/components/ComponentB.vue'
export default {
name: 'App',
components: {
ComponentA
}
}
</script>組件B(ComponetB):
<template>
<div>
I am component B
<component-c v-bind="$attrs"></component-c>
</div>
</template>
<script>
import ComponentC from '@/components/ComponentC.vue'
export default {
name: 'ComponentB',
inheritAttrs: false,
components: {
ComponentC
}
}
</script>組件C(ComponetC):
<template>
<div>
I am component C
</div>
</template>
<script>
export default {
name: 'ComponentC',
props: {
name: {
type: String
}
},
mounted: function() {
console.log('$attrs', this.$attrs)
}
}
</script>這里有三個(gè)組件,祖先組件(ComponentA)、父組件(ComponentB)和子組件(ComponentC)。這三個(gè)組件構(gòu)成了一個(gè)典型的子孫組件之間的關(guān)系。
ComponetA 給 ComponetB 傳遞了三個(gè)屬性 name、age 和 sex,ComponentB 通過(guò) v-bind="$attrs" 將這三個(gè)屬性再透?jìng)?/strong>給 ComponentC, 最后在 ComponentC 中打印 $attrs 的值為:
{age: '24', sex: 'male'}為什么我們一開(kāi)始傳遞了三個(gè)屬性,最后只打印了兩個(gè)屬性 age 和 sex 呢?因?yàn)樵?ComponentC 的props 中聲明了 name 屬性,$attrs 會(huì)自動(dòng)排除掉在 props 中聲明的屬性,并將其他屬性以對(duì)象的形式輸出。
說(shuō)白了就是一句話,$attrs 可以獲取父組件中綁定的非 Props 屬性。
一般在使用的時(shí)候會(huì)同時(shí)和 inheritAttrs 屬性配合使用。
如果你不希望組件的根元素繼承 attribute,你可以在組件的選項(xiàng)中設(shè)置 inheritAttrs: false。
在 ComponentB 添加了 inheritAttrs=false 屬性后,ComponentB 的dom結(jié)構(gòu)中可以看到是不會(huì)繼承父組件傳遞過(guò)來(lái)的屬性:

如果不加上 inheritAttrs=false 屬性,就會(huì)自動(dòng)繼承父組件傳遞過(guò)來(lái)的屬性:

再看下 $listeners 的定義:
包含了父作用域中的 (不含
.native修飾器的)v-on事件監(jiān)聽(tīng)器。它可以通過(guò)v-on="$listeners"傳入內(nèi)部組件——在創(chuàng)建更高層次的組件時(shí)非常有用。
$listeners也能把父組件中對(duì)子組件的事件監(jiān)聽(tīng)全部拿到,這樣我們就能用一個(gè)v-on把這些來(lái)自于父組件的事件監(jiān)聽(tīng)傳遞到下一級(jí)組件。
繼續(xù)改造 ComponentB 組件:
<template>
<div>
I am component B
<component-c v-bind="$attrs" v-on="$listeners"></component-c>
</div>
</template>
<script>
import ComponentC from '@/components/ComponentC.vue'
export default {
name: 'ComponentB',
inheritAttrs: false,
components: {
ComponentC
}
}
</script>這里利用 $attrs 和 $listeners 方法,可以將祖先組件(ComponentA) 中的屬性和事件透?jìng)鹘o孫組件(ComponentC),這樣就可以實(shí)現(xiàn)隔代組件之間的通信。
provide/inject
provide/inject 是 Vue 2.2.0 版本后新增的方法。
這對(duì)選項(xiàng)需要一起使用,以允許一個(gè)祖先組件向其所有子孫后代注入一個(gè)依賴,不論組件層次有多深,并在其上下游關(guān)系成立的時(shí)間里始終生效。如果你熟悉 React,這與 React 的上下文特性很相似。
先看下簡(jiǎn)單的用法:
父級(jí)組件:
export default {
provide: {
name: 'Lin'
}
}子組件:
export default {
inject: ['name'],
mounted () {
console.log(this.name); // Lin
}
}上面的例子可以看到,父組件通過(guò) privide 返回的對(duì)象里面的值,在子組件中通過(guò) inject 注入之后可以直接訪問(wèn)到。
但是需要注意的是,provide 和 inject 綁定并不是可響應(yīng)的,按照官方的說(shuō)法,這是刻意為之的。
也就是說(shuō)父組件 provide 里面的name屬性值變化了,子組件中 this.name 獲取到的值不變。
如果想讓 provide 和 inject 變成可響應(yīng)的,有以下兩種方式:
- provide 祖先組件的實(shí)例,然后在子孫組件中注入依賴,這樣就可以在子孫組件中直接修改祖先組件的實(shí)例的屬性,不過(guò)這種方法有個(gè)缺點(diǎn)就是這個(gè)實(shí)例上掛載很多沒(méi)有必要的東西比如props,methods
- 使用 Vue 2.6 提供的 Vue.observable 方法優(yōu)化響應(yīng)式 provide
看一下第一種場(chǎng)景:
祖先組件組件(ComponentA):
export default {
name: 'ComponentA',
provide() {
return {
app: this
}
},
data() {
return {
appInfo: {
title: ''
}
}
},
methods: {
fetchAppInfo() {
this.appInfo = { title: 'Welcome to Vue world'}
}
}
}我們把整個(gè) ComponentA.vue 的實(shí)例 this 對(duì)外提供,命名為 app。接下來(lái),任何組件只要通過(guò) inject 注入 app 的話,都可以直接通過(guò) this.app.xxx 來(lái)訪問(wèn) ComponentA.vue 的 data、computed、methods 等內(nèi)容。
子組件(ComponentB):
<template>
<div>
{{ title }}
<button @click="fetchInfo">獲取App信息</button>
</div>
</template>
<script>
export default {
name: 'ComponentB',
inject: ['app'],
computed: {
title() {
return this.app.appInfo.title
}
},
methods: {
fetchInfo() {
this.app.fetchAppInfo()
}
}
}
</script>這樣,任何子組件,只要通過(guò) inject 注入 app 后,就可以直接訪問(wèn)祖先組件中的數(shù)據(jù)了,同時(shí)也可以調(diào)用祖先組件提供的方法修改祖先組件的數(shù)據(jù)并反應(yīng)到子組件上。
當(dāng)點(diǎn)擊子組件(ComponentB)的獲取App信息按鈕,會(huì)調(diào)用 this.app.fetchAppInfo 方法,也就是訪問(wèn)祖先組件(ComponentA)實(shí)例上的 fetchAppInfo 方法,fetchAppInfo 會(huì)修改fetchAppInfo的值。同時(shí)子組件(ComponentB)中會(huì)監(jiān)聽(tīng) this.app.appInfo 的變化,并將變化后的title值顯示在組件上。
再看一下第二種場(chǎng)景,通過(guò) Vue.observable 方法來(lái)實(shí)現(xiàn) provide 和 inject 綁定并可響應(yīng)。
基于上面的示例,改造祖先組件(ComponentA):
import Vue from 'vue'
const state = Vue.observable({ title: '' });
export default {
name: 'ComponentA',
provide() {
return {
state
}
}
}使用 Vue.observable 定義一個(gè)可響應(yīng)的對(duì)象 state,并在 provide 中返回這個(gè)對(duì)象。
改造子組件(ComponentB):
<template>
<div>
{{ title }}
<button @click="fetchInfo">獲取App信息</button>
</div>
</template>
<script>
export default {
name: 'ComponentInject',
inject: ['state'],
computed: {
title() {
return this.state.title
}
},
methods: {
fetchInfo() {
this.state.title = 'Welcome to Vue world22'
}
}
}
</script>與之前的例子不同的是,這里我們直接修改了 this.state.title 的值,因?yàn)?state 被定義成了一個(gè)可響應(yīng)的數(shù)據(jù),所以 state.title 的值被修改后,視圖上的 title 也會(huì)立即響應(yīng)并更新,從這里看,其實(shí)很像 Vuex 的處理方式。
以上兩種方式對(duì)比可以發(fā)現(xiàn),第二種借助于 Vue.observable 方法實(shí)現(xiàn) provide 和 inject 的可響應(yīng)更加簡(jiǎn)單高效,推薦大家使用這種方式。
基于 $parent/$children 實(shí)現(xiàn)的 dispatch 和 broadcast
先了解下 dispatch 和 broadcast 兩個(gè)概念:
- dispatch: 派發(fā),指的是從一個(gè)組件內(nèi)部向上傳遞一個(gè)事件,并在組件內(nèi)部通過(guò)
$on進(jìn)行監(jiān)聽(tīng) - broadcast: 廣播,指的是從一個(gè)組件內(nèi)部向下傳遞一個(gè)事件,并在組件內(nèi)部通過(guò)
$on進(jìn)行監(jiān)聽(tīng)
在實(shí)現(xiàn) dispatch 和 broadcast 方法之前,先來(lái)看一下具體的使用方法。有 ComponentA.vue 和 ComponentB.vue 兩個(gè)組件,其中 ComponentB 是 ComponentA 的子組件,中間可能跨多級(jí),在 ComponentA 中向 ComponentB 通信:
組件ComponentA:
<template>
<button @click="handleClick">派發(fā)事件</button>
</template>
<script>
import Emitter from '../mixins/emitter.js';
export default {
name: 'ComponentA',
mixins: [Emitter],
methods: {
handleClick () {
this.dispatch('ComponentB', 'on-message', 'Hello Vue.js')
}
}
}
</script>組件ComponentB:
export default {
name: 'ComponentB',
created () {
this.$on('on-message', this.showMessage)
},
methods: {
showMessage (text) {
console.log(text)
}
}
}dispatch 的邏輯寫在 emitter.js 中,使用的時(shí)候通過(guò) mixins 混入到組件中,這樣可以很好的將事件通信邏輯和組件進(jìn)行解耦。
dispatch 的方法有三個(gè)傳參,分別是:需要接受事件的組件的名字(全局唯一,用來(lái)精確查找組件)、事件名和事件傳遞的參數(shù)。
dispatch 的實(shí)現(xiàn)思路非常簡(jiǎn)單,通過(guò) $parent 獲取當(dāng)前父組件對(duì)象,如果組件的name和接受事件的name一致(dispatch方法的第一個(gè)參數(shù)),在父組件上調(diào)用 $emit 發(fā)射一個(gè)事件,這樣就會(huì)觸發(fā)目標(biāo)組件上 $on 定義的回調(diào)函數(shù),如果當(dāng)前組件的name和接受事件的name不一致,就遞歸地向上調(diào)用此邏輯。
dispath:
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
}
}
}broadcast邏輯和dispatch的邏輯差不多,只是一個(gè)是通過(guò) $parent 向上查找,一個(gè)是通過(guò) $children 向下查找,
export default {
methods: {
broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params))
} else {
broadcast.apply(child, [componentName, eventName].concat([params]))
}
})
}
}
}到此這篇關(guān)于Vue組件間的通信方式詳析的文章就介紹到這了,更多相關(guān)Vue組件通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
elementUI中input回車觸發(fā)頁(yè)面刷新問(wèn)題與解決方法
這篇文章主要給大家介紹了關(guān)于elementUI中input回車觸發(fā)頁(yè)面刷新問(wèn)題與解決方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用elementUI具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-07-07
element-ui中el-form-item內(nèi)的el-select該如何自適應(yīng)寬度
自從用了element-ui,確實(shí)好用,該有的組件都有,但是組件間的樣式都固定好了,下面這篇文章主要給大家介紹了關(guān)于element-ui中el-form-item內(nèi)的el-select該如何自適應(yīng)寬度的相關(guān)資料,需要的朋友可以參考下2022-11-11
詳解vuex中mutations方法的使用與實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了vuex中mutations方法的使用與實(shí)現(xiàn)的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起了解一下2023-11-11
vue裁切預(yù)覽組件功能的實(shí)現(xiàn)步驟
這篇文章主要介紹了vue裁切預(yù)覽組件功能的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-05-05
vue中數(shù)據(jù)不響應(yīng)的問(wèn)題及解決
這篇文章主要介紹了vue中數(shù)據(jù)不響應(yīng)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
element中TimePicker時(shí)間選擇器禁用部分時(shí)間(顯示禁用到分鐘)
這篇文章主要介紹了element中TimePicker時(shí)間選擇器禁用部分時(shí)間(顯示禁用到分鐘),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
element-ui中樣式覆蓋問(wèn)題的方法總結(jié)
我們?cè)谑褂胑lement-ui的時(shí)候經(jīng)常會(huì)遇到需要修改組件默認(rèn)樣式,下面這篇文章主要給大家介紹了關(guān)于element-ui中樣式覆蓋問(wèn)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03
把vue-router和express項(xiàng)目部署到服務(wù)器的方法
下面小編就為大家分享一篇把vue-router和express項(xiàng)目部署到服務(wù)器的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02

