react高階面試題中有這麼一道:爲何異步請求數據在didMount階段更合適?同爲MVVM中的翹楚,Vue是否也有相似問題呢?另外,我在平時也無開發過程當中也會發現,每一個人選擇的那個生命週期階段去異步請求數據總會不同,所以引起思考,到底哪一個階段更適合異步請求數據呢?在產品設計和用戶體驗方面又會有哪些影響?本篇記錄就是爲了解決這兩個問題。javascript
1、Vue生命週期vue
首先再老話重提過一下Vue生命週期,以及每一個階段都作了什麼事。java
1. beforeCreated:生成$options選項,並給實例添加生命週期相關屬性。在實例初始化以後,在 數據觀測(data observer) 和event/watcher 事件配置以前被調用,也就是說,data,watcher,methods都不存在這個階段。可是有一個對象存在,那就是$route,所以此階段就能夠根據路由信息進行重定向等操做。node
2. created:初始化與依賴注入相關的操做,會遍歷傳入methods的選項,初始化選項數據,從$options獲取數據選項(vm.$options.data),給數據添加‘觀察器’對象並建立觀察器,定義getter、setter存儲器屬性。在實例建立以後被調用,該階段能夠訪問data,使用watcher、events、methods,也就是說 數據觀測(data observer) 和event/watcher 事件配置 已完成。可是此時dom尚未被掛載。該階段容許執行http請求操做。react
3. beforeMount:將HTML解析生成AST節點,再根據AST節點動態生成渲染函數。相關render函數首次被調用(劃重點)。面試
4. mounted:在掛載完成以後被調用,執行render函數生成虛擬dom,建立真實dom替換虛擬dom,並掛載到實例。能夠操做dom,好比事件監聽ajax
5. beforeUpdate:$vm.data更新以後,虛擬dom從新渲染以前被調用。在這個鉤子能夠修改$vm.data,並不會觸發附加的衝渲染過程。typescript
6. updated:虛擬dom從新渲染後調用,若再次修改$vm.data,會再次觸發beforeUpdate、updated,進入死循環。bash
7. beforeDestroy:實例被銷燬前調用,也就是說在這個階段仍是能夠調用實例的。dom
8. destroyed:實例被銷燬後調用,全部的事件監聽器已被移除,子實例被銷燬。
總結來講,虛擬dom開始渲染是在beforeMount時,dom實例掛載完成在mounted階段顯示。
那麼接下來了解就是render函數。
render示例:export default {
data () {
return {
menu_items: [] // 請求返回如:[{fullname: '頁面一'},{fullname: '頁面二'},{fullname: '頁面三'},{fullname: '頁面四'}]
}
}, render (createElement){ return createElement(
// 1. 第一個參數,要渲染的標籤名稱(必填)
'ul',
// 2. 第二個參數,1中要渲染的標籤的屬性,或者文本元素(可選) {
class: {'uk-nav': true}, }, // 3. 第三個參數,1中標籤的子元素,詳情看官方文檔(可選)
this.menu_items.map(item=>createElement('li',item.fullname)))
) }}複製代碼
render函數最終返回的是createNodeDescription(節點描述),即俗稱virtual node(虛擬節點)。用template寫的話,就是下面這樣:
<template>
<ul> <li v-for="item in menu_items"> {{ item.fullname }} </li> </ul>
</template>複製代碼
這個過程在mounted被調用前完成。詳細參考可移步 這裏
2、異步加載
setTimeout等異步函數
異步函數跟同步函數的不一樣之處,最大的應該就是異步函數會等到全部同步函數執行完成以後再執行。具體的能夠看 事件循環 。
//data字段有個num
created: function () {
console.group('created 建立完畢狀態===============》')
console.log('%c%s', 'color:red', 'el : ' + this.$el) // undefined
console.log('%c%s', 'color:red', 'data : ' + this.$data) // 已被初始化
console.log('%c%s', 'color:red', 'message: ' + this.message) // 已被初始化
//新增代碼片斷
setTimeout(() => { //這裏只是爲了偷懶用了ES6的箭頭函數,若是是普通函數請注意this指針修改,vue中請不要濫用箭頭函數,出了問題找都找不到
this.num ++
this.num += 2
}, 0) //注意這裏的延時都是0
setTimeout(() => {
this.num -= 5
}, 0)
}複製代碼
控制檯答應結果:
(略失真。。。截圖太大稍微壓縮了下!!--)
vue在執行代碼的時候,並無去管定時器裏發生了什麼事情,甚至已經設置了0延時,他依舊會去順序執行其餘生命週期,看起來就像跳過了這些異步加載。所以能夠肯定一點,生命週期中的異步操做不會按照順序執行,而是會等到非異步操做結束後執行。所以書寫這部分代碼的時候請注意裏面的邏輯不要和順序掛鉤,要確保任何異步操做即便最後執行,以前的程序也不會發生異常從而阻塞整個進程。
ajax異步請求
ajax請求是異步操做,回調函數的執行時間是不肯定的。也就是說,即便在created鉤子發送請求,dom被掛載以後請求仍沒有返回結果,就頗有可能致使運行出錯,諸如:
由於此時上述render事例中的menu_items仍是空置。
解決方案
針對ajax異步請求,這樣的錯誤緣由其實就是由於返回結果沒遇上dom節點的渲染。因此能夠從兩方面作修改:一是返回結果的賦值變量上,另外一個就是dom節點的渲染層面。
1. 給予賦值變量初始值,即定義時menu_items:[ {fullname: ''} ]。
這麼作的好處就是頁面節點的渲染不受限於返回結果,靜態文案照樣會被渲染,動態數據則會在數據更新時被填充。給用戶的感受就是,頁面渲染速度不錯。
可是這種方式也有缺陷,後臺返回數據字段不盡相同,要是都這麼寫那就真是麻煩了。
固然若是你使用typescript就沒有這種煩惱,menu_items: { [propName: string]: any } = {}就搞定了。
2. v-if,控制dom節點的掛載,當且僅當menu_items被賦予返回值時,纔開始渲染節點。
這麼作的好處就是靜態和動態文案同步展示在用戶面前,不會有文案跳動,數據從無到有的過程。可是,反作用就是頁面渲染時間、用戶等待時間變長。
那若是dom掛載前請求數據已經返回了,又會是怎樣的結果呢?
咱們能夠用setTimeout來模擬一下這個過程
<span>{{person.name.firstName}}</span>
data: function () {
return {
message: 'hello world',
add: 1,
person: {
name: {}
}
}
},
created: function () {
console.group('created 建立完畢狀態===============》')
console.log('%c%s', 'color:red', 'el : ' + this.$el) // undefined
console.log('%c%s', 'color:red', 'data : ' + this.$data) // 已被初始化
console.log('%c%s', 'color:red', 'message: ' + this.message) // 已被初始化
//僞裝接口返回了一些信息給你,如一我的,而後你把這些信息賦值給了this實力
setTimeout(() => {
this.person = {
name: {
lastName: 'carry',
firstName: 'dong'
},
sex: '男'
}
}, 0)複製代碼
請求夠早了吧,但仍是報錯了,this.person.name.firstName 是 undefined,不過程序報完錯後仍是再繼續執行了。
3、結論
既然異步函數並不會阻塞vue生命週期整個進程,那麼在哪一個階段請求均可以。若是考慮到用戶體驗方面的影響,但願用戶今早感知頁面已加載,減小空白頁面時間,建議就放在created階段了,而後再處理會出現null、undefined這種狀況就好。畢竟越早獲取數據,在mounted實例掛載的時候渲染也就越及時。
固然即便是這種狀況下,也不排除會觸發updated生命鉤子(data有默認值且已渲染,以後數據被更新),從而致使虛擬dom的從新渲染。