前言: 以前寫過一篇文章《在不一樣場景下Vue組件間的數據交流》,但如今來看,其中關於「父子組件通訊」的介紹仍有諸多缺漏或者不當之處, 正好這幾天學習了關於用sync修飾符作父子組件數據雙向綁定的的用法, 因而決定寫一篇文章, 再次總結下「Vue中的父子組件通訊」。
前面提示:本文文字略少,代碼略多html
父子組件通信,可分爲兩種狀況:vue
1. 父組件向子組件中傳遞數據
2. 子組件向父組件中傳遞數據
通常狀況下, 1中狀況可經過props解決數據傳遞的問題, 這裏就很少贅述了。react
主要談談2中情景的實現,有三種方式:數組
一. 經過props,父組件向子組件中傳遞數據和改變數據的函數,經過在子組件中調用父組件傳過來的函數,達到更新父組件數據(向父組件傳遞數據)的做用(子組件中須要有相應的響應事件)
二. 經過在子組件中觸發一個 自定義事件(vm.$emit),將數據做爲vm.$emit方法的參數,回傳給父組件用v-on:[自定義事件]監聽的函數
三.經過ref對子組件作標記,父組件能夠經過vm.$refs.[子組件的ref].[子組件的屬性/方法]這種方式直接取得子組件的數據 dom
下面我將一 一展現函數
這裏就不作代碼展現了
一來是由於相對比較簡單
二來是由於這種方式顯然不是Vue中的最佳實踐(在react中倒比較常見)
想要看代碼的話能夠看這裏:《【Vue】淺談Vue不一樣場景下組件間的數據交流》http://www.cnblogs.com/penghuwan/p/7286912.html#_label1 (在兄弟組件的數據交流那一節)學習
咱們能夠在子組件中經過$emit(event, [...參數])觸發一個自定義的事件,這樣,父組件能夠在使用子組件的地方直接用 v-on來監聽子組件觸發的事件, 而且能夠在監聽函數中依次取得全部從子組件傳來的參數
例如:
在子組件中某個部分寫入:this
this.emit('eventYouDefined', arg);
而後你就能夠在父組件的子組件模板裏監聽:
// 這裏是父組件的Template:spa
<Son v-on: eventYouDefined = "functionYours" />
下面是一個實例3d
父組件
<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函數做爲參數接傳參受到, 從而完成了從子組件向父組件中的傳參過程
對於咱們上面講的一和二的處理情景來講,有個侷限性就是它們都須要以事件機制爲基礎(不管是像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:
還沒有點擊「接受數據」按鈕前:
點擊接受數據按鈕後:
經過以上三種方式, 我想你應該能解決絕大多數父子組件通訊的場景了,但讓咱們再仔細考慮一下上面的通訊場景,就會發現它們還可能存在的問題:
從子組件向父組件傳遞數據時,父子組件中的數據仍不是每時每刻都同步的
但在某些特殊的需求場景下,咱們可能會但願父子組件中的數據時刻保持同步, 這時候你可能會像下面這樣作:
這是父組件中的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] + 1 this.$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提供的語法糖使得雙向綁定的代碼變得很簡單
從壞處上看:
它破環了單向數據流的簡潔性, 這增長了分析數據時的難度
咱們對上面的例子修改一下, 把數據包裹在一個對象中傳遞下來:
父組件
<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] += 1 this.$emit('update:analysisData', newObj) } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> button { float: left } </style>
demo同上
父組件的數據傳遞給子組件, 通常經過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.analysisData obj[dataName] += 1 } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> button { float: left } </style>
demo同上, 但這並非值得推薦的作法
【完】