背景与总结
先说结论:
有很多所谓的最佳实践告诉你这个观点,但其实这个观点是非常片面的,甚至在大部分场景下他是错的。
声明一下:
本文通过几个案例说明部分同行观点的片面性,分析了这种观点出现原因,涉及一点vue
的渲染原理。最后给出作为个人为了避免困扰,可以采取的相对最近实践(因人而异)。
分析
很多文章给出的理由是:mounted
回调函数被调用的时候,组件已经被挂载到了DOM
上,而比mounted
更早的生命周期还没有挂载。
这句话本身没有毛病,但它和mounted
里执行异步请求没有必然的因果关系。
关键就在于,mounted
回调执行时,组件已经挂载到了DOM
上,可我们的异步请求与DOM
有什么关系吗?
并没有直接的关系,虽然我们请求的结果需要通过DOM
呈现在界面上,用户才能看到。
但是,别忘了,通常我们的请求结果是经过处理然后复制给data
的一个属性,就像这样:
getData(){
axios.get(url).then(response => {
// 这里只是一个复制操作
this.dataset = response.data
})
}
我们只是做了一个赋值操作,并没有操作DOM
。
之后这个赋值操作会被vue
拦截到,然后去调用render
函数生成新的vnode
,进一步去做diff
找出需要更新的属性、节点、样式等,最后会有真正的读写DOM
的操作。
这也正是为什么都说要放在mounted
回调中执行异步请求的原因,因为赋值操作会导致DOM
的操作。
但这个理由根本不成立,关键就在于异步这俩字。
如果你要做一个同步的请求,那么我同意上述的观点,但不好意思,你不是同步的。
案例
来看两个例子对比下:
第一个,我们在created
中有个异步操作,修改了requested
属性。
<template>
<h1>{{ requested }}</h1>
</template>
<script>
export default {
data() {
return {
requested: '',
}
},
created() {
console.log('created')
setTimeout(() => {
this.requested = 'done!'
console.log('setTimeout!')
})
},
mounted() {
console.log('mounted')
},
}
</script>
结果:代码正常运行,界面正常展示,并没有发生你所担心的created
中读写DOM
出错的问题。而且从输出的日志可以看出来,异步回调是在mounted
之后才执行的,这个很关键。
如果换成同步操作呢?
<template>
<h1>{{ requested }}</h1>
</template>
<script>
export default {
data() {
return {
requested: '',
}
},
created() {
console.log('created')
this.requested = 'requested finished!'
console.log('requested finished!')
},
mounted() {
console.log('mounted')
},
}
</script>
结果:代码正常运行,界面正常展示,也没有发生你所担心的created
中读写DOM
出错的问题。
而从输出的日志可以看出来,created
中的同步操作是在mounted
之前执行的。
这里很奇怪,尽管我们修改了data
中的一个状态会导致vue
去读写DOM
,但此时并没有发生错误。
Vue
渲染原理(部分)
实际上,这里需要简单了解下vue
的渲染流程-diff
,diff
会对oldVnode
和newVnode
进行对比,在开始的时候有个很简单的逻辑,以下是伪代码:
if (!oldVnode){
// 没有上一次的 vnode,说明是首次渲染
// 直接执行挂载方法,不需要再进行其它的比较
mountComponent()
} else {
// 正常的属性、子节点等的对比
}
所以,created
中同步的修改状态不用担心读写DOM
出错的问题。
异步请求放在哪里
业内同行给出mounted
中执行异步代码的建议是相对安全的一种选择,对于部分初学者来说,避免了业务开发的难度,避免了疑难问题的出现频率。
由于前端各种开发人员对vue
的原理认识不足,凭着自身的经验给出这个最佳实践是可以理解的。
实际上,通过上面的案例可以看到,将异步/同步请求放在created
里执行是可以的,当然有个前提哈:请求的回调中不能直接读写DOM
。
甚至,你异步(同步不行)请求放在beforeCreate
中执行都是可行的,请求的回调中不能直接读写DOM
依然是前提。
<template>
<h1>{{ requested }}</h1>
</template>
<script>
export default {
data() {
return {
requested: '',
}
},
beforeCreate() {
console.log('beforeCreate')
setTimeout(() => {
this.requested = 'done!'
console.log('setTimeout!')
})
},
created() {
console.log('created')
},
mounted() {
console.log('mounted')
},
}
</script>
结果:
代码执行运行,界面正常展示,很匪夷所思吧。
哈哈,有个重要的点,vue
中的渲染流程init
、beforeCreate
、created
、beforeMount
、mounted
等生命周期钩子的调用是同步的,所以异步的请求回调都会在这些同步钩子执行完后才可能执行(可能的意思是,还得等其它同步任务执行完成)。
这里的原理涉及到js
的事件循环,不过多展开了,有兴趣可以看看这篇文章【JS】深入理解事件循环,这一篇就够了!。
另外,以上分析案例都是基于vue2.x
的版本,实际上,使用vue3.x
,结论依然成立。
综上所述
- 如果你是初学者或者只是临时使用
vue
实现简单的业务,不想过多的深入,那么直接在mounted
中执行异步代码一般没有明显的问题; - 如果你是追求更好的性能,以及更深入的理解
vue
,那么不妨更深入的思考下最佳实践原因,它是否适合你以及你的项目呢。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。