起因
在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是微任务还是宏任务?