Vue中用props給data賦初始值遇到的問題

2018-11-28更:文章發佈後由於存在理解錯誤,經@Kim09AI同窗提醒後作了調整,在此深表感謝。其餘不足之處,還望不吝賜教。css

前言

前段時間作一個運營活動的項目,上線後產品反饋頁面埋點不對,在排查過程當中發現,問題居然是因爲Vue中的data初始值致使,而data的初始值來自於props。爲方便描述,現將問題抽象以下:html

1、現象

代碼:

<!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>

代碼解讀:

  1. 根組件data中有一個對象:user,包含三個屬性:name、gender、birthday,初始值都爲空字符串
  2. 模擬api異步請求,500毫秒後對user的從新賦值,三個屬性都再也不爲空
  3. 聲明一個子組件userInfo,props中有一個對象userData,用於接收父組件的user;data中有一個變量userName,初始值來自於userData.name

結果:

clipboard.png

頁面初始化後,姓名、性別、生日都顯示爲空,500毫秒後性別和生日顯示正常結果,僅姓名沒有變化。vue

爲何會這樣呢?react

2、緣由及解決辦法

我最初的想法:user.name是String,屬於基本數據類型,用它給子組件data中userName賦值,屬於基本數據類型賦值,因此當父組件中user.name變化時,子組件中userName並不會隨之變化。api

是這樣的嗎?因而我決定將user.name改成對象,經過引用數據類型賦值,而後觀察是否符合預期。代碼以下:app

<!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>

運行結果:異步

clipboard.png

完美!!!ide

若是咱們不想把user.name改成Object類型,有沒有其餘的解決辦法呢?ui

既然基本數據類型賦值無法實現值同步,那咱們能夠考慮監聽props中的值,而後手動變動局部變量。基於此,咱們很天然的就想到Vue中有監聽做用的兩個功能:watch、computedthis

爲了縮減篇幅,咱們此處只貼出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>
        `
    });

經驗證,結果符合皆預期!

3、走過的彎路

第一條彎路

詳見評論區@Kim09AI同窗的評論。

第二條彎路

其實,曾覺得致使文章開頭的問題,是因爲data在初始化後深拷貝,props再次變化data並不會刷新致使的。

直到文章發佈之初,仍然持此觀點,後來經@Kim09AI同窗提醒才恍然大悟。

當初之因此深信是data被深拷貝致使的,主要是本身在翻到Vue官方文檔看到關於data的描述:

clipboard.png

看到"遞歸地」那個詞,就想固然地認爲data被深拷貝了,由於深拷貝的核心原理就是遞歸。

其實如今再回過頭來看那段描述,包括在Reactivity in Depth一章的描述:

clipboard.png

它們真正含義是:Vue會遞歸地遍歷data全部的屬性,並使用Object.defineProperty把這些屬性所有轉爲getter/setter,讓data中的屬性更具「交互性」,以此做爲實現雙向綁定的基礎。包括還順便解釋了一下爲何Vue不支持IE8的緣由:IE8不支持Object.defineProperty。

這也僅僅解釋了爲何只有在組件初始化之初data中已經聲明的屬性才具備「交互性」,即data中屬性的變化會引發視圖變化,而其餘在最初data中沒有聲明的屬性則不會。正如在The Vue Instance所說:

clipboard.png

小結一下:

  • 文章開頭的問題是一個關於基本數據類型和引用數據類型賦值的問題
  • data在初始化時被遞歸遍歷轉化是用於實現雙向綁定

這麼看來,兩者是沒有任何關係的。

4、關於Vue中props的要點

過後又仔細翻了一下關於props的文檔:

clipboard.png

大概梳理一下:
1.props是單向數據流:父組件的數據變化,經過props實時反應在子組件中,反之否則

2.不容許在子組件中直接操做props

3.能夠變相操做props
(1)在data中聲明局部變量,並用props初始化
(2)在computed中對props值轉換後輸出

5、一點反思

分享是一種知識的傳遞,嚴謹和正確是最重要的,技術文章更是如此。想固然和不加深究實爲大忌,引覺得戒。

相關文章
相關標籤/搜索