前言: 之前寫過一篇文章《在不同場景下Vue組件間的數據交流》,但現在來看,其中關于“父子組件通信”的介紹仍有諸多缺漏或者不當之處, 正好這幾天學習了關于用sync修飾符做父子組件數據雙向綁定的的用法, 于是決定寫一篇文章, 再次總結下“Vue中的父子組件通信”。
前面提示:本文文字略少,代碼略多
父子組件通訊,可分為兩種情況:
1. 父組件向子組件中傳遞數據
2. 子組件向父組件中傳遞數據
一般情況下, 1中情況可通過props解決數據傳遞的問題, 這里就不多贅述了。
?
子組件向父組件中傳遞數據
?
主要談談2中情景的實現,有三種方式:
一. 通過props,父組件向子組件中傳遞數據和改變數據的函數,通過在子組件中調用父組件傳過來的函數,達到更新父組件數據(向父組件傳遞數據)的作用(子組件中需要有相應的響應事件)
二. 通過在子組件中觸發一個 自定義事件(vm.$emit),將數據作為vm.$emit方法的參數,回傳給父組件用v-on:[自定義事件]監聽的函數
三.通過ref對子組件做標記,父組件可以通過vm.$refs.[子組件的ref].[子組件的屬性/方法]這種方式直接取得子組件的數據
?
下面我將一 一展示
一. 通過props從父向子組件傳遞函數,調用函數改變父組件數據
這里就不做代碼展示了
一來是因為相對比較簡單
二來是因為這種方式顯然不是Vue中的最佳實踐(在react中倒比較常見)
想要看代碼的話可以看這里:《【Vue】淺談Vue不同場景下組件間的數據交流》http://www.cnblogs.com/penghuwan/p/7286912.html#_label1 (在兄弟組件的數據交流那一節)
?
二.? 通過自定義事件從子組件向父組件中傳遞數據
我們可以在子組件中通過$emit(event, [...參數])觸發一個自定義的事件,這樣,父組件可以在使用子組件的地方直接用 v-on來監聽子組件觸發的事件, 并且可以在監聽函數中依次取得所有從子組件傳來的參數
例如:
在子組件中某個部分寫入:
this.emit(
'eventYouDefined', arg);
然后你就可以在父組件的子組件模板里監聽:
// 這里是父組件的Template:
<Son v-on: eventYouDefined =
"functionYours" />
下面是一個實例
父組件
<template><div id=
"father"><div>
我是父組件,我接受到了:{{ text ||
'暫無數據' }}<son v-on:sendData=
'getSonText'></son></div></div>
</template><script>
import son from './son.vue'
export default {data: function () {return {text: ''}},components: {son: son},methods: {getSonText (text) {this.text =
text}}
}</script><style scoped>
#father div {padding: 10px;margin: 10px;border: 1px solid grey;overflow: hidden;
}
</style>
?
子組件:
<template><div><p>我是子組件,我所擁有的數據: {{ text }}</p><button @click=
"sendData">
發送數據</button></div>
</template><script>
export default {data () {return {text: '來自子組件的數據'}},methods: {sendData () {this.$emit(
'sendData',
this.text)}}
}
</script><!-- Add
"scoped" attribute to limit CSS to
this component only -->
<style scoped>
button { float: left }
</style>
?
在點擊子組件中的“發送數據”按鈕前, 父組件還沒有接受到數據(text為空字符串), 則通過? {{ text || '暫無數據'? }}將顯示默認文本:‘暫無數據’
點擊“發送數據”按鈕后:
?
因為sendData自定義事件被觸發,通過?
this.$emit(
'sendData',
this.text)
//此處的this指向子組件實例) 子組件的text數據被父組件中:
<son v-on:sendData=
'getSonText'></son>
中的getSonText函數作為參數接傳參受到, 從而完成了從子組件向父組件中的傳參過程
?
三. 通過ref屬性在父組件中直接取得子組件的數據(data)
對于我們上面講的一和二的處理情景來說,有個局限性就是它們都需要以事件機制為基礎(無論是像click那樣的原生事件還是自定義事件),而在事件發生的時候才能調用函數將數據傳遞過來
但如果子組件里沒有類似“按鈕”的東西,因而無法制造原生事件,同時也沒辦法找到一個觸發自定義事件的時機的時候,怎么從子組件向父組件傳遞數據呢??
這個時候, 我們就只能從父組件中“直接取”子組件的數據了,借助ref屬性
ref是我們經常用到的Vue屬性,利用它可以簡單方便地從本組件的template中取得DOM實例,而實際上,如果你在父組件中為子組件設置ref的話, 就可以直接通過vm.$refs.[子組件的ref].[子組件的屬性]去拿到數據啦,例如:
父組件:
<template><div id=
"father"><div>
我是父組件,我接受到了:{{ text ||
'暫無數據' }}<button @click=
"getSonText()">接受數據</button><son
ref=
'son'></son></div></div>
</template><script>
import son from './son.vue'
export default {data: function () {return {text: ''}},components: {son: son},methods: {getSonText () {this.text =
this.$refs.son.text}}
}</script><style scoped>
#father div {padding: 10px;margin: 10px;border: 1px solid grey;overflow: hidden;
}
</style>
?
子組件:
<template><div><p>我是子組件,我所擁有的數據: {{ text }}</p></div>
</template><script>
export default {data () {return {text: '來自子組件的數據'}}
}
</script><!-- Add
"scoped" attribute to limit CSS to
this component only -->
<style scoped>
button { float: left }
</style>
?
demo:
尚未點擊“接受數據”按鈕前:
點擊接受數據按鈕后:
?
通過sync實現數據雙向綁定, 從而同步父子組件數據
通過以上三種方式, 我想你應該能解決絕大多數父子組件通信的場景了,但讓我們再仔細考慮一下上面的通信場景,就會發現它們還可能存在的問題:
從子組件向父組件傳遞數據時,父子組件中的數據仍不是每時每刻都同步的
?
但在某些特殊的需求場景下,我們可能會希望父子組件中的數據時刻保持同步, 這時候你可能會像下面這樣做:
這是父組件中的template:
<son :foo=
"bar" v-on:update=
"val => bar = val"></son>
在子組件中, 我們通過props聲明的方式接收foo并使用
props: {foo: [type]
} ?
同時每當子組件中數據改變的時候,通過
?
this.$emit(
'update', newValue)
把參數newValue傳遞給父組件template中監聽函數中的"val"。然后通過
?
val => bar = val
?
這個表達式就實現了bar = newValue. 這個時候,我們發現父組件中的關鍵數據bar被子組件改變(相等)了!
通過數據的雙向綁定, 父(組件)可以修改子的數據, 子也可以修改父的數據
Vue提供了sync修飾符簡化上面的代碼,例如:
<comp :foo.sync=
"bar"></comp>
會被擴展為:
<comp :foo=
"bar" @update:foo=
"val => bar = val"></comp>
然后你需要在子組件中改變父組件數據的時候, 需要觸發以下的自定義事件:
this.$emit(
"update:foo", newValue)
【注意】你可能覺得這好像和我上面提到的二中的“通過自定義事件(emit)從子組件向父組件中傳遞數據”的那一節的內容似乎重疊了,。
然而并不是, 兩者有著父子組件關系上的不同, 下面我通過一行關鍵的代碼證明它們的區別所在
1.在我們講解sync的這一小節里, 自定義事件發生時候運行的響應表達式是:
<son :foo="bar" v-on:update="val => bar = val"></son> 中的 "val => bar = val"
2.在二中的“通過自定義事件從子組件向父組件中傳遞數據” 里,自定義事件發生時候運行的響應表達式是:
<Son? v-on: eventYouDefined = "arg => functionYours(arg)" /> 中的 "arg => functionYours(arg)"
對前者, 表達式 val => bar = val意味著強制讓父組件的數據等于子組件傳遞過來的數據, 這個時候,我們發現父子組件的地位是平等的。 父可以改變子(數據), 子也可以改變父(數據)
對后者, 你的functionYours是在父組件中定義的, 在這個函數里, 你可以對從子組件接受來的arg數據做任意的操作或處理, 決定權完全落在父組件中, 也就是:? 父可以改變子(數據), 但子不能直接改變父(數據)!, 父中數據的變動只能由它自己決定
下面是一個展示demo:
父組件:
<template><div id=
"father"><div>
我是父組件<
son:wisdom.sync=
"wisdom":magic.sync=
"magic":attack.sync=
"attack":defense.sync=
"defense"></son><p>智力: {{ wisdom }}</p><p>膜法: {{ magic }}</p><p>攻擊: {{ attack }}</p><p>防御: {{ defense }}</p></div></div>
</template><script>
import son from './son.vue'
export default {data: function () {return {wisdom: 90,magic: 160,attack: 100,defense: 80}},components: {son: son}
}</script><style scoped>
#father div {padding: 10px;margin: 10px;border: 1px solid grey;overflow: hidden;
}
</style>
?
子組件:
<template><div><p>我是子組件</p><p>智力: {{ wisdom }}</p><p>膜法: {{ magic }}</p><p>攻擊: {{ attack }}</p><p>防御: {{ defense }}</p><button @click=
"increment('wisdom')">增加智力</button><button @click=
"increment('magic')">增加膜法</button><button @click=
"increment('attack')">增加攻擊</button><button @click=
"increment('defense')">增加防御</button></div>
</template><script>
export default {props: {wisdom: Number,magic: Number,attack: Number,defense: Number},methods: {increment (dataName) {let newValue =
this[dataName] +
1this.$emit(`update:${dataName}`, newValue)}}
}
</script><!-- Add
"scoped" attribute to limit CSS to
this component only -->
<style scoped>
button { float: left }
</style>
?
點擊前:
?
點擊增加子組件中“增加智力”按鈕的時候, 父組件和子組件中的智力參數同時從90變為91
點擊增加子組件中“增加膜法”按鈕的時候, 父組件和子組件中的智力參數同時從160變為161
?
數據雙向綁定是把雙刃劍
從好處上看:
1.它實現了父子組件數據的“實時”同步, 在某些數據場景下可能會使用到這一點
2.sync提供的語法糖使得雙向綁定的代碼變得很簡單
從壞處上看:
它破環了單向數據流的簡潔性, 這增加了分析數據時的難度
?
當sync修飾的prop是個對象
我們對上面的例子修改一下, 把數據包裹在一個對象中傳遞下來:
父組件
<template><div id=
"father"><div>
我是父組件<son :analysisData.sync=
"analysisData"></son><p>智力: {{ analysisData.wisdom }}</p><p>膜法: {{ analysisData.magic }}</p><p>攻擊: {{ analysisData.attack }}</p><p>防御: {{ analysisData.defense }}</p></div></div>
</template><script>
import son from './son.vue'
export default {data: function () {return {analysisData: {wisdom: 90,magic: 160,attack: 100,defense: 80}}},components: {son: son}
}</script><style scoped>
#father div {padding: 10px;margin: 10px;border: 1px solid grey;overflow: hidden;
}
</style>
?
子組件:
<template><div><p>我是子組件</p><p>智力: {{ analysisData.wisdom }}</p><p>膜法: {{ analysisData.magic }}</p><p>攻擊: {{ analysisData.attack }}</p><p>防御: {{ analysisData.defense }}</p><button @click=
"increment('wisdom')">增加智力</button><button @click=
"increment('magic')">增加膜法</button><button @click=
"increment('attack')">增加攻擊</button><button @click=
"increment('defense')">增加防御</button></div>
</template><script>
export default {props: {analysisData: Object},methods: {increment (dataName) {let newObj = JSON.parse(JSON.stringify(
this.analysisData))newObj[dataName] +=
1this.$emit(
'update:analysisData', newObj)}}
}
</script><!-- Add
"scoped" attribute to limit CSS to
this component only -->
<style scoped>
button { float: left }
</style>
?
?demo同上
不要通過在子組件中修改引用類型props達到“父子組件數據同步”的需求!
父組件的數據傳遞給子組件, 一般通過props實現, 而在實現“父子組件數據同步”這一需求的時候, 小伙伴們可能會發現一點: 在子組件中修改引用類型的props(如數組和對象)是可行的
1.不僅可以達到同時修改父組件中的數據(因為本來引用的就是同一個數據)
2.而且還不會被Vue的檢測機制發現!(不會報錯)
但千萬不要這樣做, 這樣會讓數據流變得更加難以分析,如果你嘗試這樣做, 上面的做法可能會更好一些
不要這樣做,糟糕的做法:
父組件:
<template><div id=
"father"><div>
我是父組件<son :analysisData=
"analysisData"></son><p>智力: {{ analysisData.wisdom }}</p><p>膜法: {{ analysisData.magic }}</p><p>攻擊: {{ analysisData.attack }}</p><p>防御: {{ analysisData.defense }}</p></div></div>
</template><script>
import son from './son.vue'
export default {data: function () {return {analysisData: {wisdom: 90,magic: 160,attack: 100,defense: 80}}},components: {son: son}
}</script><style scoped>
#father div {padding: 10px;margin: 10px;border: 1px solid grey;overflow: hidden;
}
</style>
?
子組件:
<template><div><p>我是子組件</p><p>智力: {{ analysisData.wisdom }}</p><p>膜法: {{ analysisData.magic }}</p><p>攻擊: {{ analysisData.attack }}</p><p>防御: {{ analysisData.defense }}</p><button @click=
"increment ('wisdom')">增加智力</button><button @click=
"increment ('magic')">增加膜法</button><button @click=
"increment ('attack')">增加攻擊</button><button @click=
"increment ('defense')">增加防御</button></div>
</template><script>
export default {props: {analysisData: Object},methods: {increment (dataName) {let obj =
this.analysisDataobj[dataName] +=
1}}
}
</script><!-- Add
"scoped" attribute to limit CSS to
this component only -->
<style scoped>
button { float: left }
</style>
?
demo同上, 但這并不是值得推薦的做法
?【完】
?
?
?
總結
以上是生活随笔為你收集整理的【Vue】Vue中的父子组件通讯以及使用sync同步父子组件数据的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。