背景
??Vue是單頁面應(yīng)用,單頁面應(yīng)用又是由組件構(gòu)成,各個組件之間又互相關(guān)聯(lián)体捏,那么如何實(shí)現(xiàn)組件之間通信就顯得尤為重要了。就像人是由各種器官組成蔬墩,那么組件之間的通信就像是血液一樣將營養(yǎng)(數(shù)據(jù))輸送到各個部位译打,為了保證數(shù)據(jù)流向的簡潔性,使程序更易于理解拇颅,所以vue提倡單項(xiàng)數(shù)據(jù)流。組件之間的通信主要分為三種乔询,父子組件通信樟插、孫子組件通信和非關(guān)聯(lián)組件通信。
父子組件通信
props和$emit
??這種方式是大家最經(jīng)常用到的竿刁,父組件通過v-bind綁定數(shù)據(jù)黄锤,子組件通過props接收父組件傳過來的數(shù)據(jù),利用$emit觸發(fā)指定事件食拜,父組件通過$on監(jiān)聽子組件觸發(fā)的對應(yīng)事件鸵熟,這里就不舉例了。主要講一下prop:
- Vue數(shù)據(jù)是單向數(shù)據(jù)流负甸,這樣設(shè)計(jì)的目的是為了保證數(shù)據(jù)流向的簡潔性流强,使程序更易于理解,每次父級組件發(fā)生更新時呻待,子組件中所有prop都將會刷新為最新的值打月,prop會在子組件創(chuàng)建之前傳遞,所以可以在data和computed中直接使用蚕捉。
- 不應(yīng)該在一個子組件內(nèi)部改變prop奏篙,這樣會破壞單向的數(shù)據(jù)綁定,導(dǎo)致數(shù)據(jù)流難以理解迫淹。如果有這樣的需要秘通,可以通過 data 屬性接收或使用 computed 屬性進(jìn)行轉(zhuǎn)換为严。
- 如果prop傳遞的是基本類型,這時候改變prop的數(shù)據(jù)就會報錯肺稀,如果是引用數(shù)據(jù)類型如果改變原始數(shù)據(jù)不會報錯梗脾,但是重新賦值或改變某個屬性值就會報錯,利用這一點(diǎn)就能夠?qū)崿F(xiàn)父子組件數(shù)據(jù)的“雙向綁定”盹靴,雖然這樣實(shí)現(xiàn)能夠節(jié)省代碼炸茧,但會犧牲數(shù)據(jù)流向的簡潔性,令人難以理解稿静,最好不要這樣去做梭冠。想要實(shí)現(xiàn)父子組件的數(shù)據(jù)“雙向綁定”,可以使用 v-model 或 .sync改备,下面會講到控漠。
v-model
v-model實(shí)現(xiàn)父子組件數(shù)據(jù)的雙向綁定,它的本質(zhì)是v-bind和v-on的語法糖悬钳,在一個組件上使用v-model盐捷,默認(rèn)會為組件綁定名為value的屬性和名為input的事件。例如:
<test-model
v-bind:value="haorooms"
v-on:input="haorooms=$event"></test-model>
<script>
export default {
data () {
haorooms: ''
}
}
</script>
等價于
<test-model v-model="haorooms"></test-model>
<script>
export default {
data () {
haorooms: ''
}
}
</script>
子組件
<template>
<div>
<input
v-bind:value="value"
v-on="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
props: ['value'],
model: {
prop: 'value',
event: 'input'
}
}
</script>
.sync
.sync修飾符它的本質(zhì)和v-model類似默勾,它的本質(zhì)也是v-bind和v-on的語法糖碉渡,例如:
<test-model
v-bind:title="doc.title"
v-on:update:title="doc.title=$event"></test-model>
<script>
export default {
data () {
doc: {
title: ''
}
}
}
</script>
等價于
<test-model v-bind:title.sync="doc.title"></test-model>
<script>
export default {
data () {
doc: {
title: ''
}
}
}
</script>
子組件
<template>
<div>
<input v-model="value">
</div>
</template>
<script>
export default {
data () {
value: ''
},
watch: {
value (val) {
this.$emit('update:title', val)
}
}
}
</script>
這種是綁定一個值的情況,還可以綁定多個值:
<template>
<div id="demo">
<test-model v-bind.sync="haorooms"></test-model>
</div>
</template>
<script>
import testModel from './testModel'
export default {
data(){
return{
haorooms: {
name: 'aaa',
age: 18,
value: 10
}
}
},
components: {
testModel,
},
watch: {
haorooms: {
handler (val) {
console.log('test', val)
},
deep: true
}
}
}
</script>
子組件
<template>
<div></div>
</template>
<script>
export default {
data () {
return {
test: ''
}
},
mounted () {
this.$emit('update:name', 111)
}
}
</script>
我們看到這種方式是將haorooms對象中name母剥、age滞诺、value三個屬性都實(shí)現(xiàn)了雙向綁定,在子組件中觸發(fā)事件的時候需要指定某個屬性來觸發(fā)环疼。
注意帶有 .sync 修飾符的 v-bind 不能和表達(dá)式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是無效的)习霹。取而代之的是,你只能提供你想要綁定的屬性名炫隶,類似 v-model淋叶。
將 v-bind.sync 用在一個字面量的對象上,例如 v-bind.sync=”{ title: doc.title }”伪阶,是無法正常工作的煞檩,因?yàn)樵诮馕鲆粋€像這樣的復(fù)雜表達(dá)式的時候,有很多邊緣情況需要考慮望门。
v-model和.sync對比
共同點(diǎn):
- v-model和.sync都可以實(shí)現(xiàn)父子組件數(shù)據(jù)的雙向綁定形娇,本質(zhì)都是v-bind和v-on的語法糖。
不同點(diǎn): - v-model默認(rèn)會為組件綁定名為value的屬性和名為input的事件筹误,而.sync可以自定義傳入的屬性和事件桐早。
- v-model只能實(shí)現(xiàn)某一個屬性的雙向綁定,.sync可以實(shí)現(xiàn)多個屬性的雙向綁定。
- 寫法不同v-model需要在子組件中寫porp來接收數(shù)據(jù)哄酝,而.sync是利用$attrs來接收數(shù)據(jù)友存,所以不需要寫prop。
$parent陶衅、$children和ref
??這三種方式都是通過直接得到組件實(shí)例屡立,可以實(shí)現(xiàn)父子組件、兄弟組件搀军、跨級組件等數(shù)據(jù)通信膨俐,不過一般不建議使用,因?yàn)闀黾咏M件間的耦合罩句,而且要判斷組件存不存在焚刺,如果不存在可能會遇到報錯,這幾種方式比較簡單也不在這里舉例了门烂。
跨級組件
$attrs和$listeners
$attrs
背景
??隨著項(xiàng)目復(fù)雜度的提高乳愉,組件嵌套的層級越來越深,之前的組件通信一般使用v-bind和prop組合使用屯远,但是我們發(fā)現(xiàn)這種方式只是適用于父子組件蔓姚,如果是孫子組件的話就需要將父組件的數(shù)據(jù)傳遞給子組件,子組件的數(shù)據(jù)再傳遞給孫組件慨丐,這樣子就需要寫很多prop坡脐,有沒有哪種方式可以直接將父組件直接傳遞給孫組件,讓代碼更加簡潔咖气?這就是$attrs的由來挨措,解決跨組件數(shù)據(jù)傳遞,注意只對孫子組件有效崩溪,但是class和style數(shù)據(jù)傳遞除外。
使用
首先我們有三個嵌套組件父A-子B-孫C斩松,然后我們想讓A中的數(shù)據(jù)傳入C中伶唯,用prop的做法是這樣子的:
<div id="app">
A{{msg}}
<component-b :msg="msg"></component-b>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '100'
},
components: {
'ComponentB': {
props: ['msg'],
template: `<div>B<component-c :msg="msg"></component-c></div>`,
components: {
'ComponentC': {
props: ['msg'],
template: '<div>C{{msg}}</div>'
}
}
},
}
})
</script>
組件B并沒有使用父組件傳遞過來的msg,而是直接傳遞給組件C惧盹,除了這樣子有沒有什么方式直接將數(shù)據(jù)傳遞給C呢乳幸,下面我們來看$attrs的寫法:
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '100'
},
components: {
'ComponentB': {
template: `<div>B<component-c v-bind="$attrs"></component-c></div>`,
components: {
'ComponentC': {
props: ['msg'],
template: '<div>C{{msg}}</div>'
}
}
},
}
})
</script>
總結(jié):為了解決跨組件通信,而提出了$attrs钧椰。prop和$attrs都可以用來父子組件通信粹断,接收父組件傳遞過來的數(shù)據(jù),但是prop的優(yōu)先級高于$attrs嫡霞,如果子組件中prop瓶埋、$attrs都有寫,那么數(shù)據(jù)只會被prop接收,注意$attrs不能接收class和style傳過來的數(shù)據(jù)养筒。
inheritAttrs
背景
<template>
<div class="home">
<mytest :title="title" :message="message"></mytest>
</div>
</template>
<script>
export default {
name: 'home',
data () {
return {
title:'title1111',
message:'message111'
}
},
components:{
'mytest':{
template:`<div>這是個h1標(biāo)題{{title}}</div>`,
props:['title'],
data(){
return{
meg:'111'
}
},
created:function(){
console.log(this.$attrs)//注意這里
}
}
}
}
</script>
??上面的代碼曾撤,我們在組件里只是用了title這個屬性,message屬性沒有用到晕粪,那么下瀏覽器渲染出來是什么樣呢挤悉?如下圖:
我們看到:組件內(nèi)未被注冊的屬性將作為普通html元素屬性在子組件的根元素上渲染,雖然在一般情況下不會對子組件造成影響巫湘,但是就怕遇到一些特殊情況装悲,比如:
<template>
<childcom :name="name" :age="age" type="text"></childcom>
</template>
<script>
export default {
'name':'test',
props:[],
data(){
return {
'name':'張三',
'age':'30',
'sex':'男'
}
},
components:{
'childcom':{
props:['name','age'],
template:`<input type="number" style="border:1px solid blue">`,
}
}
}
</script>
我們看到父組件的type="text"覆蓋了input上type="number",這不是我想要的尚氛,我需要input上type=number類型不變诀诊,但是我還是想取到父組件上的type="text"的值,這時候inheritAttrs就派上用場了怠褐。
<template>
<childcom :name="name" :age="age" type="text"></childcom>
</template>
<script>
export default {
'name':'test',
props:[],
data(){
return {
'name':'張三',
'age':'30',
'sex':'男'
}
},
components:{
'childcom':{
inheritAttrs:false,
props:['name','age'],
template:`<input type="number" style="border:1px solid blue">`,
created () {
console.log(this.$attrs.type)
}
}
}
}
</script>
總結(jié):默認(rèn)情況下父組件傳遞數(shù)據(jù)給子組件但是沒被prop特性綁定將會回退且作為普通的html特性應(yīng)用在子組件的根元素上畏梆。inheritAttrs屬性用來去掉這種默認(rèn)行為,來避免不可預(yù)知的影響奈懒。注意 inheritAttrs: false 選項(xiàng)不會影響 style 和 class 的綁定奠涌。
$listeners
背景
上面講了$attrs是為了跨組件傳遞數(shù)據(jù),那如果想通過孫子組件來給父組件傳遞數(shù)據(jù)呢磷杏?之前的做法也是一層一層的向上傳遞溜畅,比如用$emit方法,但是子組件如果用不到极祸,只是想改變父組件的數(shù)據(jù)慈格,這時候我們就可以使用$listeners。
<template>
<div>
<childcom :name="name" :age="age" :sex="sex" @testChangeName="changeName"></childcom>
</div>
</template>
<script>
export default {
'name':'test',
props:[],
data(){
return {
'name':'張三',
'age':'30',
'sex':'男'
}
},
components:{
'childcom':{
props:['name'],
template:`<div>
<div>我是子組件 {{name}}</div>
<grandcom v-bind="$attrs" v-on="$listeners"></grandcom>
</div>`,
components: {
'grandcom':{
template:`<div>我是孫子組件-------<button @click="grandChangeName">改變名字</button></div>`,
methods:{
grandChangeName(){
this.$emit('testChangeName','kkkkkk')
}
}
}
}
}
},
methods:{
changeName(val){
this.name = val
}
}
}
</script>
$listeners是一個對象遥金,里面包含了作用在這個組件上的所有監(jiān)聽器浴捆。
參考文章:http://www.reibang.com/p/ce8ca875c337
provide和inject
背景
??項(xiàng)目越復(fù)雜,組件嵌套的層級就越深稿械,那么子組件怎樣實(shí)現(xiàn)跟祖先組件的通信問題选泻,這就是provide和inject提出的原因。
簡介
??這個方法允許一個祖先組件向其所有子孫后代注入一個依賴美莫,不論組件嵌套的層次有多深页眯,并在起上下游關(guān)系成立的時間里始終有效。一言以蔽之:祖先組件中通過provider來提供變量厢呵,然后在子孫組件中通過inject來注入變量窝撵。例如:
假設(shè)有兩個組件:A和B,A是B組件的祖先組件
// A.vue
export default {
provide: {
name: '天涯'
}
}
// B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 天涯
}
}
我們可以看到在祖先組件A中提供了一個變量襟铭,那么在其所有的后代組件中都可以注入這個變量并使用碌奉。
所以短曾,上面 A.vue 的 name 如果改變了,B.vue 的 this.name 是不會改變的道批,仍然是天涯错英。
實(shí)際上,你可以把依賴注入看作一部分“大范圍有效的 prop”隆豹,除了:
- 祖先組件不需要知道哪些后代組件使用它提供的屬性
- 后代組件不需要知道被注入的屬性來自哪里
然而椭岩,依賴注入還是有負(fù)面影響的。它將你應(yīng)用程序中的組件與它們當(dāng)前的組織方式耦合起來璃赡,使重構(gòu)變得更加困難判哥。同時所提供的屬性是非響應(yīng)式的。這是出于設(shè)計(jì)的考慮碉考,因?yàn)槭褂盟鼈儊韯?chuàng)建一個中心化規(guī)乃疲化的數(shù)據(jù)跟使用$root做這件事都是不夠好的。如果你想要共享的這個屬性是你的應(yīng)用特有的侯谁,而不是通用化的锌仅,或者如果你想在祖先組件中更新所提供的數(shù)據(jù),那么這意味著你可能需要換用一個像Vuex這樣真正的狀態(tài)管理方案了墙贱。
非關(guān)聯(lián)組件通信
vuex
??vuex想必大家都非常熟悉了热芹,它是vue的狀態(tài)管理管理中心,存儲的數(shù)據(jù)是響應(yīng)式的惨撇,但并不會保存起來伊脓,刷新之后就回到了初始狀態(tài),具體做法應(yīng)該在vuex數(shù)據(jù)發(fā)生改變的時候把數(shù)據(jù)拷貝一份保存到localStorage里面魁衙,這樣子也可以實(shí)現(xiàn)實(shí)現(xiàn)父子組件报腔、兄弟組件、跨級組件剖淀、非關(guān)聯(lián)組件等數(shù)據(jù)通信纯蛾。在這里也不再舉例。
eventBus
??它的實(shí)現(xiàn)思想也很好理解纵隔,在要互相通信的兩個組件中茅撞,都引入同一個新的vue實(shí)例,然后在兩個組件中通過分別調(diào)用這個實(shí)例的事件觸發(fā)和監(jiān)聽來實(shí)現(xiàn)通信巨朦。
//eventBus.js
import Vue from 'vue';
export default new Vue();
<!--組件A-->
<script>
import Bus from 'eventBus.js';
export default {
methods: {
sayHello() {
Bus.$emit('sayHello', 'hello');
}
}
}
</script>
<!--組件B-->
<script>
import Bus from 'eventBus.js';
export default {
created() {
Bus.$on('sayHello', target => {
console.log(target); // => 'hello'
});
}
}
</script>
$root
??通過$root根組件任何組件都可以獲取當(dāng)前組件樹的根vue實(shí)例,通過維護(hù)根實(shí)例上的data剑令,就可以實(shí)現(xiàn)組件間的數(shù)據(jù)共享糊啡。
// 組件A
<script>
export default {
created() {
this.$root.$emit('changeTitle', '我是A')
}
}
</script>
// 組件B
<script>
export default {
created() {
this.$root.$on('changeTitle')
},
methods: {
changeTitle (title) {
console.log(title)
}
}
}
</script>
這種方式有個弊端就是組件A和組件B必須同時存在。下面用另外一種方式可以優(yōu)化:
//main.js 根實(shí)例
new Vue({
el: '#app',
store,
router,
// 根實(shí)例的 data 屬性吁津,維護(hù)通用的數(shù)據(jù)
data: function () {
return {
author: ''
}
},
components: { App },
template: '<App/>',
});
<!--組件A-->
<script>
export default {
created() {
this.$root.author = '于是乎'
}
}
</script>
<!--組件B-->
<template>
<div><span>本文作者</span>{{ $root.author }}</div>
</template>
??通過這種方式棚蓄,雖然可以實(shí)現(xiàn)通信堕扶,但在應(yīng)用的任何部分,任何時間發(fā)生的任何數(shù)據(jù)變化梭依,都不會留下變更的記錄稍算,這對于稍復(fù)雜的應(yīng)用來說,調(diào)試是致命的役拴,不建議在實(shí)際應(yīng)用中使用糊探。
總結(jié):
本文講了組件之間的各種通信:
父子組件通信:prop和$emit、v-model河闰、.sync科平、$parent、children和ref
父子組件雙向綁定:props和$emit姜性、v-model瞪慧、.sync
跨級組件通信:$attrs和$listeners、provide和inject
非關(guān)聯(lián)組件通信:vuex部念、eventBus弃酌、$root