原文地址:https://segmentfault.com/a/1190000017149162
2018-11-28更:文章發布后因為存在理解錯誤,經@Kim09AI同學提醒后做了調整,在此深表感謝。其他不足之處,還望不吝賜教。
前言
前段時間做一個運營活動的項目,上線后產品反饋頁面埋點不對,在排查過程中發現,問題竟然是由於Vue中的data初始值導致,而data的初始值來自於props。為方便描述,現將問題抽象如下:
一、現象
代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用props初始化data中變量</title> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script> </head> <body> <div id="app"> <user-info :user-data="user"></user-info> </div> <script> //全局組件 let userInfo = Vue.component('userInfo' ,{ name: 'user-info', props: { userData: Object }, data() { return { userName: this.userData.name } }, template: ` <div> <div>姓名:{{userName}}</div> <div>性別:{{userData.gender}}</div> <div>生日:{{userData.birthday}}</div> </div> ` }); //Vue實例 new Vue({ el: '#app', data: { user: { name: '', gender: '', birthday: '' } }, created(){ this.getUserData(); }, methods:{ getUserData(){ setTimeout(()=>{ this.user.name = '於永雨'; this.user.gender = '男'; this.user.birthday = '1991-7'; }, 500) } }, components: { userInfo } }); </script> </body> </html>
代碼解讀:
- 根組件data中有一個對象:user,包含三個屬性:name、gender、birthday,初始值都為空字符串
- 模擬api異步請求,500毫秒后對user的重新賦值,三個屬性都不再為空
- 聲明一個子組件userInfo,props中有一個對象userData,用於接收父組件的user;data中有一個變量userName,初始值來自於userData.name
結果:
頁面初始化后,姓名、性別、生日都顯示為空,500毫秒后性別和生日顯示正常結果,僅姓名沒有變化。
為什么會這樣呢?
二、原因及解決辦法
我最初的想法:user.name是String,屬於基本數據類型,用它給子組件data中userName賦值,屬於基本數據類型賦值,所以當父組件中user.name變化時,子組件中userName並不會隨之變化。
是這樣的嗎?於是我決定將user.name改為對象,通過引用數據類型賦值,然后觀察是否符合預期。代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用props初始化data中變量-對象形式</title> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script> </head> <body> <div id="app"> <user-info :user-data="user"></user-info> </div> <script> //全局組件 let userInfo = Vue.component('userInfo' ,{ name: 'user-info', props: { userData: Object }, data() { return { userName: this.userData.name } }, template: ` <div> <div>姓名:{{userName.text}}</div> <div>性別:{{userData.gender}}</div> <div>生日:{{userData.birthday}}</div> </div> ` }); //Vue實例 new Vue({ el: '#app', data: { user: { name: {text: ''}, gender: '', birthday: '' } }, created(){ this.getUserData(); }, methods:{ getUserData(){ setTimeout(()=>{ this.user.name.text = '於永雨'; this.user.gender = '男'; this.user.birthday = '1991-7'; }, 500) } }, components: { userInfo } }); </script> </body> </html>
運行結果:
完美!!!
如果我們不想把user.name改為Object類型,有沒有其他的解決辦法呢?
既然基本數據類型賦值沒法實現值同步,那我們可以考慮監聽props中的值,然后手動變更局部變量。基於此,我們很自然的就想到Vue中有監聽作用的兩個功能:watch、computed。
為了縮減篇幅,我們此處只貼出userInfo組件,其他代碼與第一個示例一致,具體如下:
方法一:watch
let userInfo = Vue.component('userInfo' ,{ name: 'user-info', props: { userData: Object }, data() { return { userName: this.userData.name } }, watch: { 'userData.name': function (val) { //監聽props中的屬性 this.userName = val; } }, template: ` <div> <div>姓名:{{ userName }}</div> <div>性別:{{ userData.gender }}</div> <div>生日:{{ userData.birthday }}</div> </div> ` });
方法二:computed
let userInfo = Vue.component('userInfo' ,{ name: 'user-info', props: { userData: Object }, data() { return { userName: this.userData.name } }, computed: { computedUserName(){ return this.userData.name } }, template: ` <div> <div>姓名:{{ computedUserName }}</div> <div>性別:{{ userData.gender }}</div> <div>生日:{{ userData.birthday }}</div> </div> ` });
經驗證,結果符合皆預期!
三、走過的彎路
第一條彎路
詳見評論區@Kim09AI同學的評論。
第二條彎路
其實,曾以為導致文章開頭的問題,是由於data在初始化后深拷貝,props再次變化data並不會刷新導致的。
直到文章發布之初,仍然持此觀點,后來經@Kim09AI同學提醒才恍然大悟。
當初之所以深信是data被深拷貝導致的,主要是自己在翻到Vue官方文檔看到關於data的描述:
看到"遞歸地”那個詞,就想當然地認為data被深拷貝了,因為深拷貝的核心原理就是遞歸。
其實現在再回過頭來看那段描述,包括在Reactivity in Depth一章的描述:
它們真正含義是:Vue會遞歸地遍歷data所有的屬性,並使用Object.defineProperty把這些屬性全部轉為getter/setter,讓data中的屬性更具“交互性”,以此作為實現雙向綁定的基礎。包括還順便解釋了一下為什么Vue不支持IE8的原因:IE8不支持Object.defineProperty。
這也僅僅解釋了為什么只有在組件初始化之初data中已經聲明的屬性才具有“交互性”,即data中屬性的變化會引起視圖變化,而其他在最初data中沒有聲明的屬性則不會。正如在The Vue Instance所說:
小結一下:
- 文章開頭的問題是一個關於基本數據類型和引用數據類型賦值的問題
- data在初始化時被遞歸遍歷轉化是用於實現雙向綁定
這么看來,二者是沒有任何關系的。
四、關於Vue中props的要點
事后又仔細翻了一下關於props的文檔:
大概梳理一下:
1.props是單向數據流:父組件的數據變化,通過props實時反應在子組件中,反之不然
2.不允許在子組件中直接操作props
3.可以變相操作props
(1)在data中聲明局部變量,並用props初始化
(2)在computed中對props值轉換后輸出
五、一點反思
分享是一種知識的傳遞,嚴謹和正確是最重要的,技術文章更是如此。想當然和不加深究實為大忌,引以為戒。