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的从新渲染。