vue中須要注意的問題總結(上)

文章首發於我的博客javascript

前言

使用vue的時候常常會遇到一些問題,其實仔細閱讀查閱官方文檔,就會發現文檔中已提到一些格外須要注意的點; 爲了深刻的理解官方文檔中對這些問題的解釋,查閱了一些資料,再加上本身的理解,整理了一些常見的問題;若是哪方面解釋的不太合理但願各路大神指出;html

文章篇幅較長,可是很實用;vue

目錄

  • 組件裏面, data必須是一個函數
  • vue中$set的使用場景
  • vue生命週期詳解
  • vue組件通訊
  • vue組件之keep-alive
  • 生命週期函數/methods/watch裏面不該該使用箭頭函數
  • methods/computed/watch

1.組件裏面, data必須是一個函數

類比引用數據類型 Object是引用數據類型, 每一個組件的data 都是內存的同一個地址,一個數據改變了其餘也改變了;java

那麼用什麼方法可使每一個組件的data相互獨立,不受影響呢?git

當一個組件被定義,data 必須聲明爲返回一個初始數據對象的函數,由於組件可能被用來建立多個實例。若是 data 仍然是一個純粹的對象,則全部的實例將共享引用同一個數據對象!經過提供 data 函數,每次建立一個新實例後,咱們可以調用 data 函數,從而返回初始數據的一個全新副本數據對象。es6

2.vue中$set的使用場景

場景1:

經過數組的下標去修改數組的值,數據已經被修改了,可是不觸發updated函數,視圖不更新,github

export default {
    data () {
        return {
            items: ['a', 'b', 'c']
        };
    },
    updated () {
        console.log('數據更新', this.items[0]);
    },
    methods: {
        changeItem1 () {
            this.items[0] = 'x';
            console.log(111, this.items[0]);
        },
        changeItem2 () {
            this.$set(this.items, 0, 'x');
            console.log(222, this.items[0]);
        },
    }
};
複製代碼

執行changeItem1, 控制檯打印 111 'x', 沒有觸發updated,視圖不更新 執行changeItem2, 控制檯打印 222 'x', 數據更新 'x'; 觸發updated,視圖更新vuex

場景2: vue中檢測不到對象屬性的添加和刪除

data() {
     userProfile: {
        name: '小明',
    }
}
複製代碼

想要給userProfile加一個age屬性後端

addProperty () {
     this.userProfile.age = '12';
     console.log(555, this.userProfile);
}
複製代碼

執行addProperty函數時,打印以下api

555 { name: '小明', age: '12'}
複製代碼

可是沒有觸發updated, 視圖未更新 改爲下面這種

addProperty () {
      this.$set(this.userProfile, 'age', '12');
      console.log(666, this.userProfile);
 }
複製代碼

再次執行, 數據發生變化, 觸發updated, 視圖更新;

有時你想向已有對象上添加多個屬性,例如使用 Object.assign() 或 _.extend() 方法來添加屬性。可是,添加到對象上的新屬性不會觸發更新。在這種狀況下能夠建立一個新的對象,讓它包含原對象的屬性和新的屬性:

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
複製代碼

這是vue中很典型的一個問題,使用的時候必定要注意!

簡單的解釋一下原理:

vue在建立實例的時候把data深度遍歷全部屬性,並使用 Object.defineProperty 把這些屬性所有轉爲 getter/setter。讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。因此屬性必須在 data 對象上存在才能讓 Vue 轉換它,這樣才能讓它是響應的。

當你在對象上新加了一個屬性newProperty,當前新加的這個屬性並無加入vue檢測數據更新的機制(由於是在初始化以後添加的),vue.$set是能讓vue知道你添加了屬性, 它會給你作處理

3.vue生命週期詳解

1. vue的生命週期

  • beforeCreate: 組件實例剛剛被建立,組件屬性計算以前,如data屬性
  • created: 組件實例建立完成,屬性已綁定,可是DOM還未完成,$el屬性還不存在
  • beforeMount:模板編譯/掛載以前
  • mounted: 模板編譯/掛載以後
  • beforeUpdate: 組件更新以前
  • updated: 組件更新以後
  • activated: for keep-alive,組件被激活時調用
  • deactivated: for keep-alive,組件被移除時調用
  • beforeDestroy: 組件銷燬前被調用
  • destoryed: 組件銷燬後調用

ps:下面代碼能夠直接複製出去執行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
<body>
    <div id="app">{{a}}</div>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                a: 'vuejs',
            },
            beforeCreate: function() {
                console.log('建立前');
                console.log(this.a);
                console.log(this.$el);
            },
            created: function() {
                console.log('建立以後');
                console.log(this.a);
                console.log(this.$el);
            },
            beforeMount: function() {
                console.log('mount以前');
                console.log(this.a);
                console.log(this.$el);
            },
            mounted: function() {
                console.log('mount以後');
                console.log(this.a);
                console.log(this.$el);
            },
            beforeUpdate: function() {
                console.log('更新以前');
                console.log(this.a);
                console.log(this.$el);
            },
            updated: function() {
                console.log('更新完成');
                console.log(this.a);
                console.log(this.$el);
            },
            beforeDestroy: function() {
                console.log('組件銷燬以前');
                console.log(this.a);
                console.log(this.$el);
            },
            destroyed: function() {
                console.log('組件銷燬以後');
                console.log(this.a);
                console.log(this.$el);
            },
        })
    </script>
</body>
</html>
複製代碼

beforeCreated: el和data並未初始化 created: 完成data數據的初始化,el沒有 beforeMount: 完成了el和data初始化 mounted: 完成掛載

title

打開命令行在命令行中輸入vm.a = 'change';查看效果
複製代碼

title

4.vue組件通訊

1.父組件給子組件傳遞數據

vue中使用props向子組件傳遞數據 1): 子組件在props中建立一個屬性,用於接收父組件傳過來的值 2): 父組件中註冊子組件 3): 在子組件標籤中添加子組件props中建立的屬性 4): 把須要傳給子組件的值賦給該屬性

2.子組件向父組件傳遞數據

子組件主要經過事件傳遞數據給父組件 1), 子組件中須要以某種方式,例如點擊事件的方法來觸發一個自定義事件 2),將須要傳的值做爲$emit的第二個參數,該值將做爲實參數傳給相應自定義事件的方法 3),在父組件中註冊子組件並在子組件標籤上綁定自定義事件的監聽

3.子組件向子組件傳遞數據

vue找那個沒有直接子組件對子組件傳參的方法,建議將須要傳遞數據的在組件,都合併爲一個組件,若是必定須要子組件對子組件傳參,能夠先傳到父組件,再傳到子組件,爲了方便開發,vue推出了一個狀態管理工具vuex,能夠啃方便的實現組件之間的參數傳遞

具體的實例代碼以下:能夠自行參考相關代碼在編輯器中嘗試 父組件向子組件傳遞數據

// 父組件向子組件傳遞數據
<!--
msg 是在data中(父組件)定義的變量
若是須要從父組件中獲取logo的值,就須要使用props['msg'], 如30行
在props中添加了元素之後,就不須要在data中(子組件)中再添加變量了
-->
<template> <div> <child @transferuser="getUser" :msg="msg"></child> <p>用戶名爲:{{user}}(我是子組件傳遞給父組件的數據)</p> </div> </template>

<script>
    import child from './child.vue';
    export default {
        components: {
            child,
        },
        data() {
            return {
                user: '',
                msg: '我是父組件傳給子組件的信息',
            };
        },
        methods: {
            getUser(msg) {
                this.user = msg;
                console.log(msg);
            },
        },
    };
</script>
複製代碼

子組件向父組件傳遞數據

// 子組件向父組件傳遞數據
<!--
1.@ : 是  v-on的簡寫
2.子組件主要經過事件傳遞數據給父組件
3.當input的值發生變化時,將username傳遞給parent.vue,首先聲明瞭一個setUser,用change事件來調用setUser
4.在setUser中,使用了$emit來遍歷transferUser事件,並返回this.username,其中transferuser是一個自定義事件,功能相似一箇中轉,this.username經過這個事件傳遞給父組件
-->
<template> <div> <div>{{msg}}</div> <span>用戶名</span> <input v-model="username" @change='setUser'>向父組件傳值</button> </div> </template>

<script>
    export default {
        data() {
            return {
                username: '測試',
            };
        },
        props: {
            msg: {
                type: String,
            },
        },
        methods: {
            setUser() {
                this.$emit('transferuser', this.username);
            },
        },
    };
</script>
複製代碼

5.vue組件之keep-alive

項目中寫vue也沒注意到<keep-alive></keep-alive>這個組件,最近在深刻的研究vue組件的生命週期函數,每個函數都是幹嗎的,而後其中有activateddeactivated這兩個函數與<keep-alive></keep-alive>這個組件有關

  • activated: keep-alive組件激活時調用
  • deactivated: keep-alive組件停用時調用

keep-alive用法

  • <keep-alive>包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們
  • <keep-alive>是一個抽象組件:它自身不會渲染一個DOM元素,也不會出如今父組件鏈中
  • 當組件在<keep-alive>內被切換,它的activateddeactivated這兩個生命週期鉤子函數將會被對應執行

具體的實例以下

  • 是一個簡單的tab切換,能夠嘗試把<keep-alive>去掉以後,對比一下,而後就會發現它的好處

test.vue

<template>
    <div class="test">
        <div class="testNav">
            <div :class="{'selected':tab === 1,'testTitle':true}" @click="toTab(1)">標題一</div>
            <div :class="{'selected':tab === 2,'testTitle':true}"  @click="toTab(2)">標題二</div>
        </div>
        <div class="container">
            <keep-alive>
                <Test1 v-if="tab === 1">
                </Test1>
                <Test2 v-else>
                </Test2>
            </keep-alive>
        </div>
    </div>
</template>

<script>
    import Test1 from './test1.vue';
    import Test2 from './test2.vue';
    export default {
        data() {
            return {
                tab: 1,
            };
        },
        components: {
            Test1,
            Test2,
        },
        methods: {
            toTab(index) {
                this.tab = index;
            },
        },
    }
</script>

<style lang="less">
.test {
    width: 100%;
    .testNav {
        height: 60px;
        line-height: 60px;
        display: flex;
        border-bottom: 1px solid #e5e5e5;
        .testTitle {
            flex: 1;
            text-align: center;
        }
        .selected {
            color: red;
        }
    }
}
</style>
複製代碼

測試結果以下: 注意看一下頁面和控制檯輸出的信息,能夠更加直觀的注意到<keep-alive>的做用及activateddeactivated這兩個函數何時會被觸發

  • 打開頁面,會出現下面這樣
    1

用setTimeout模擬請求後端接口的場景

  • 點擊title2,出現下面的狀況
    2
  • 再次點擊title1,出現下面的狀況,你會發現從後端請求的數據會快速顯示出來,可是若是你此時不用
    3

test1.vuetest2.vue的相關代碼以下:

test1.vue

<template>
  <div class="test1">
      test1
      {{testInfo1}}
  </div>
</template>

<script>
    export default {
        data() {
            return {
                testInfo1: '',
            };
        },
        activated() {
            console.log('測試1被激活');
        },
        deactivated() {
            console.log('測試1被緩存');
        },
        created() {
            setTimeout(() => {
                this.testInfo1 = '這是測試一的數據';
            }, 2000);
        },
    }
</script>

複製代碼

test2.vue

<template>
  <div>
      test2
      {{testInfo2}}
  </div>
</template>

<script>
    export default {
        data() {
            return {
                testInfo2: '',
            }
        },  
        activated() {
            console.log('測試2被激活');
        },
        deactivated() {
            console.log('測試2被緩存');
        },
        created() {
            setTimeout(() => {
                this.testInfo2 = '這是測試二的數據';
            }, 2000);
        },
    }
</script>
複製代碼

6. 生命週期函數/methods/watch裏面不該該使用箭頭函數

es6的箭頭函數的出現,是咱們能夠用更少的代碼實現功能,可是應該注意箭頭函數和普通函數的最大區別是this的指向問題: 箭頭函數的this指向函數所在的所用域,普通函數的this指向函數的調用者;

官方文檔中特別提醒中已經指出這一點:

vue中生命週期函數, methods, watch 自動綁定 this 上下文到實例中,所以你能夠訪問數據,對屬性和方法進行運算。這意味着 你不能使用箭頭函數來定義一個生命週期方法, 這是由於箭頭函數綁定了父上下文,所以 this 與你期待的 Vue 實例不一樣

7.methods/computed/watch

methods VS computed

咱們能夠將同一個函數定義爲methods或者computed,用這兩種方式,獲得的結果是相同的,不一樣的是computed是基於它們的依賴進行緩存的,計算屬性只有在它相關的依賴發生改變時才從新求值;

適用場景:

從新計算開銷很大的話,選computed; 不但願有緩存的選methods

computed vs watch

watch 有新舊值兩個參數, 計算屬性沒有,可是計算屬性能夠從setter得到新值

關於computed

對於計算屬性要特別說明一點: vue的計算屬性computed默認只有getter,須要使用getter的時候須要本身加一個setter

export default {
    data () {
        return {
            firstName: '張',
            lastName: '三',
        };
    },
    computed: {
        fullName() {
              return this.firstName + ' ' + this.lastName
        },
    },
    methods: {
        changeFullName () {
            this.fullName = '李 四';
        }
    },
};

其中computed裏的代碼完整寫法是  

computed: {
   fullName: {
        // getter
        get: function () {
          return this.firstName + ' ' + this.lastName
        },
   }    
},
複製代碼

執行 changeFullName 發現報錯[Vue warn]: Computed property "fullame" was assigned to but it has no setter.

咱們須要給計算屬性fullName添加一個setter

computed: {
   fullName: {
        // getter
        get: function () {
          return this.firstName + ' ' + this.lastName
        },
        // setter
        set: function (newValue) {
          var names = newValue.split(' ')
          this.firstName = names[0]
          this.lastName = names[names.length - 1]
        }
  }    
},
複製代碼

總結

上述這些問題從vue官方文檔中均能找到答案,固然想要更深刻的理解爲何,還須要從vue源碼分析入手;

下一篇文章打算從源碼入手去解釋這些問題,理解vue總體的程序設計;

vue系列文章

相關文章
相關標籤/搜索