从vue生命周期中两个“不会保证”说起

起因

在mounted中使用ref获取一个节点中的子节点,有时会获取不到。

vue2文档-生命周期

文档中说明:

mounted 注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick

updated 注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick

再次尝试

两个不能保证,没有更多的解释,那我们改用nexttick获取,发现还是还是获取不到。

最后改用setTimeout获取成功

那么,mounted,nexttick,settimeout有什么内在原理呢?

分析

那么,文档说的“不会保证”究竟是哪种情况呢?

如果代码都是同步执行,那就不会出现父组件挂载完成子组件没挂载的情况,所以原因只能是:

异步组件

这里一种例外情况。在你的组件中,包含一个异步组件。因为异步组件一定不会第一时间实例化,自然无法保证子组件节点挂载。

没懂?

再进一步:

因为每个组件有自己的生命周期,所以当虚拟DOM树对比时,根据vue中diff的优化,只会对此组件的虚拟DOM树要更新的部分做更新。

而在某组件虚拟DOM树中,只知道其子组件的构造器(Ctor),传入的数据(data)和子项(children)有没有变化,不关心其子组件内部的虚拟DOM树。

因此,只能保证这个组件要去渲染它的子组件,却无法保证它的子组件内部是如何渲染的。

有点懂了?那有人要问了,既然异步组件无法确定何时才能实例完成,那么使用nextTick进行提取,按理说是可以获取的,然而并没有获取到。但是使用定时器延迟一定时间进行获取却获取到了,这个问题怎么解释?

原理解析

前置知识:宏任务和微任务

当使用nextTick时,将在微任务(不支持微任务的浏览器将回退至宏任务)堆栈中入栈你写的回调。

如果所有子组件都已经下载完毕,并在Vue中定义,则从根组件往后渲染时,因为用的都是同步方法,微任务将在这些同步方法后被执行,所以大多数时候可以通过nextTick获取子组件渲染后的DOM节点。

但是如果实例化组件的时候,有其他微任务入栈,就要看微任务队列的执行顺序了,所以无法完全保证。

对于异步组件,由于网络原因,import微任务总在nextTick之后,所以总是拿不到子组件的DOM。

而在setTimeout一定时间之后,由于setTimeout是宏任务,所以一般会在渲染后执行。

但对于异步组件,如果由于网络延迟,导致微任务在宏任务之后入栈,则在setTimeout的回调中仍无法获取子组件的DOM。

简单的来说,由于有异步执行顺序的影响,只能保证单独组件内部的执行顺序,无法保证其他组件的解析注册执行顺序。

清晰多了吧,再去看vue3文档

vue3文档的生命周期

这里的onMounted和onUpdated中解释多一些,可以看看帮助理解。但也不如我们探讨的深,从官方的解释也印证了我们的思考。

ps:附上小资料
nextTick是微任务还是宏任务?

posted @ 2023-03-11 14:35  无所事事者爱嘲笑  阅读(113)  评论(0编辑  收藏  举报