nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
以上既是官方关于 nextTick 的用法,也是 nextTick 的描述。
用更加直白的话来描述,就是:在 nextTick 的回调方法执行的时候,DOM 已经是更新好的了,这个时候可以执行跟真实 DOM 相关的处理方法或依赖真实 DOM 的方法。
nextTick 应用大概就是这样了,但我们就这样就结束了吗?
如果感觉意犹未尽的话,不妨来看下 nextTick 的更深层次的解读吧。
为什么需要 nextTick 这个方法
nextTick 这个方法是 Vue 单独实现的,原生方法中是没有的。不过也可以理解,这个方法的意义在于,更新 DOM 后,执行回调方法去做一些事情。原生中没有这种应用场景,所以肯定是没有的。
假如是熟悉 React 的话,nextTick 的效果大概等同于 setState 的回调 和 useState 和 useEffect的结合 。都能拿到更新后的值以及更新后的 DOM。
该阶段之所以称为
layout,因为该阶段的代码都是在DOM渲染完成(mutation阶段完成)后执行的。触发
状态更新的this.setState如果赋值了第二个参数回调函数,也会在此时调用。this.setState({ xxx: 1 }, () => { console.log("i am update~"); });
看到这里,或许会有些疑问 :为什么需要回调呢?
因为 Vue 默认使用 异步执行DOM更新 。
只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的
Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn, 0)代替。Vue.js异步更新DOM策略及nextTick.MarkDown
查看queueWatcher的源码我们发现,Watch对象并不是立即更新视图,而是被push进了一个队列queue,此时状态处于waiting的状态,这时候会继续会有Watch对象被push进这个队列queue,等到下一个tick运行时,这些Watch对象才会被遍历取出,更新视图。同时,id重复的Watcher不会被多次加入到queue中去,因为在最终渲染时,我们只需要关心数据的最终结果。
异步就是不可预测,所以如果你想要做一些跟真实 DOM 相关的事情时,你会不知道在什么时机去做会好一些。这个时候,就该到 nextTick 大展身手的时候了。
nextTick 是怎么实现的
这个实现是优先 microTask ,也就是 Promise.resolve().then() 和 MutationObserver 最后如果浏览器实在是不支持这两者,就用 setTimeout 来实现。
这两篇文章 Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心! 和 Vue.js异步更新DOM策略及nextTick.MarkDown 都提及,nextTick 为什么要优先使用 microTask 来实现。
Vue.js异步更新DOM策略及nextTick.MarkDown
为什么要优先使用microtask?我在顾轶灵在知乎的回答中学习到:
JS 的 event loop 执行时会区分 task 和 microtask,引擎在每个 task 执行完毕,从队列中取下一个 task 来执行之前,会先执行完所有 microtask 队列中的 microtask。 setTimeout 回调会被分配到一个新的 task 中执行,而 Promise 的 resolver、MutationObserver 的回调都会被安排到一个新的 microtask 中执行,会比 setTimeout 产生的 task 先执行。 要创建一个新的 microtask,优先使用 Promise,如果浏览器不支持,再尝试 MutationObserver。 实在不行,只能用 setTimeout 创建 task 了。 为啥要用 microtask? 根据 HTML Standard,在每个 task 运行完以后,UI 都会重渲染,那么在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。 反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。 参考顾轶灵知乎的回答:https://www.zhihu.com/question/55364497/answer/144215284
此外,在 Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心! 这篇文章中,还提及到以前 Vue 也曾试过使用 task (1.0.27 / 2.0.0-rc.7 uses a setImmediate shim using window.postMessage) 来实现 nextTick ,不过遇到了一些问题后,又撤回了。具体可以看这个 New timerFunc in rc7 is laggy #3771 issue。
至此,大概对 nextTick 了解更多一些了吧。
参考
New timerFunc in rc7 is laggy #3771
Vue.js异步更新DOM策略及nextTick.MarkDown