nextTick
官网定义
当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick()
可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
理解
vue中的nextTick用于在DOM更新之后(页面渲染之前)被调用,用于获取最新的DOM信息。
当我们调用nextTick
时,传给它的回调函数实际上是在当前宏任务执行完毕,并且在即将执行的微任务队列中排队。这意味着,nextTick
的回调会在DOM更新之前被调度,但它的确是在DOM实际更新的那个"tick"之后执行的。换句话说,nextTick
提供了一个在DOM更新完成后被调用的时机,而不是在Vue开始处理状态变更和更新DOM的同一"tick"中执行,而是紧随其后,在接下来的事件循环周期(即下一个"tick")里执行,确保当回调被调用时,所有的DOM变更都已经完成。使得它成为在DOM更新后再执行后续逻辑
在Vue的上下文中,"tick"是一个非官方但广泛使用的术语,用来形象地描述一个特定的时间点或事件循环阶段。具体而言,它指的是Vue在响应数据变化后,完成DOM更新的那一个时间点,或者是从Vue开始处理数据变更到DOM实际反映这些变更的整个过程。更精确地说,"tick"涵盖了Vue内部执行的以下步骤:
- 数据变更检测:Vue监控到数据模型发生变化。
- 异步队列积累:变更被记录,但不立即应用,而是加入到一个待处理的队列中。
- DOM更新:当所有同步代码执行完毕,且当前宏任务结束,Vue在微任务队列中批量处理这些变更,更新DOM。
因此,"tick"并不直接对应于JavaScript的事件循环中的宏任务或微任务,而是涵盖了Vue处理数据到DOM更新这一完整流程的抽象概念,强调的是Vue在响应数据变化后,更新视图的一整套动作序列。通过
nextTick
方法,开发者可以在这个"tick"的生命周期结束,即DOM更新完毕后,安排执行特定的逻辑或访问已更新的DOM元素。tick:变更过程或者变更结束后的一个快照
在Vue的上下文中,并没有明确地将微任务队列分为两个不同的"tick"。但是,我们可以根据Vue的更新机制和nextTick
的工作原理,来理解这两个概念之间的关系。
Vue在处理响应式数据变化时,会将这些变化记录下来,并计划在未来的某个时刻批量更新DOM。这个批量更新通常发生在当前宏任务执行完毕,且在进入下一个宏任务之前,微任务队列执行的过程中。我们可以把这个DOM更新的时机视为一个“tick”,即Vue更新DOM的那个关键点。
当调用nextTick
时,nextTick
的回调函数会被安排在DOM更新的“tick”之后立即执行的微任务队列中。这意味着,从Vue的角度看,nextTick
的回调是在一个特定的、与DOM更新密切相关的“tick”之后执行的,但它仍然是在同一个微任务队列中。
所以,虽然没有严格意义上将微任务队列分为两个不同的"tick",但可以理解为Vue处理DOM更新及其后执行nextTick
回调形成了两个连续的阶段,第一个阶段是Vue处理数据变更并更新DOM的“tick”,紧接着的第二个阶段是nextTick
回调执行的时刻,这时可以安全地访问更新后的DOM。
历史实现变更
在V2中nextTick会根据不同的浏览器兼容性,依次判断Promise、MutationObserver、setImmediate、setTimeout在该浏览器是否支持,如果支持则选用,否则以此往下兼容
在V3,舍弃兼容直接采用了Promise来实现
本质
nextTick
本质就是创建了一个微任务(不考虑setTimeout
),将其回调推入微任务队列。vue
中一个事件循环中的所有dom
更新操作也是一个微任务,两者属于同一优先级,执行先后只于入队的先后有关,换句话说,如果你先写了nextTick
,再写赋值语句(在此之前没有触发dom更新的操作),那在nextTick中获取的可就不是更新后的dom
了
const name = ref('123456');
nextTick(() => {
console.log(document.querySelector('#test').innerHTML);
});
name.value = '31231';
这种情况获取到的是31231
因为ref
创建name也可也看作一次name.value,也就是DOM数据的更新异步函数比nextTick先入微任务队列
const name = ref('123456');
name.value = '43321';
const handleClick = () => {
nextTick(() => {
console.log(document.querySelector('#test').innerHTML);
});
name.value = '31231';
};
这种情况获取到的是43321
因为函数被调用时,是nextTick先入的微任务队列。