Vue nextTick 是 Vue 内部非常重要的机制。本文假设你已经了解 microtask 和 macrotask 的区别,将从以下三个角度来介绍 nextTick:
- 静态方法 Vue.nextTick 挂载
- 实例方法 Vue.prototype.$nextTick 挂载
- nextTick 源码分析。
静态方法 Vue.nextTick
Vue.nextTick 定义于 src/core/global-api/index.js
:
1 | export function initGlobalAPI (Vue: GlobalAPI) { |
我们很少在全局中使用 nextTick 处理业务,但要知道 Vue 在初始化 globalApi 的时候暴露了这个方法。
实例方法 Vue.prototype.$nextTick
由深入浅出 Vue 实例化 一节中可知,最终的构造函数位于 src/core/instance/index.js
:
1 | import { renderMixin } from './render' |
在 renderMixin(Vue)
中定义了实例方法:
1 | export function renderMixin (Vue: Class<Component>) { |
实例方法在我们的业务代码中相对常见。用来解决在数据变更后,“立即”获取 dom 更新后的结果。
1 | 注意这里面为 callback 传入了上下文 this,也就是 Vue 实例。 |
1 | new Vue({ |
nextTick 源码分析
nextTick 源码位于 src/core/util/next-tick.js
, 在2.6.10 的版本中,代码如下:
1 | import { noop } from 'shared/util' |
我们忽略变量定义和函数定义部分。那么 nextTick 主要由两部分组成。一个是选择 microtask 还是 macrotask:
- 如果原生支持 promise。使用 promise。如果是 ios webview,需额外触发一个 setTimeout。此时表示使用 microtask。
- 如果不支持 promise, 但是支持 MutationObserver(ios7 、platformjs, android 4.4),并且不是 ie 的话,选择 mutation observer。此时表示使用 microtask。
- 以上都不支持的话,回退到 setImmediate(ie10-11)。
- 以上都不支持的话,回退到 setTimout(ie9)。
我们以 4 为例子,最终将生成一个函数 timerFunc
:
1 | timerFunc = () => { |
另外就是 nextTick 函数的定义。
nextTick 接收两个参数,cb 和上下文参数。首先将 cb 包装成一个匿名函数,push 到 callbacks 数组里。
如果当前 nextTick 在执行的话,就表示处于 pending 状态。如果非 pending 状态,则执行我们的 timerFunc。而 timeFunc 则会调用 flushCallbacks,执行所有的 callback 函数。
了解了 nextTick 行为后,我们来回顾一下深入浅出 Vue 数据驱动 (二,中 nextTick 在派发更新的流程中,是如何调用的。
1 | /** |
当我们改变了数据时,watcher 并不会立即出发,而是会放到队列里。以防重复触发一个 watcher,造成的不必要的 dom 更新。并且当前 tick 的变更会在 nextTick 去响应,在 nextTick 的流程里更新 dom。
除了在数据变化时会调用 nextTick,另外一种场景是手动调用 nextTick。我们仍以上面的例子为例:
1 | new Vue({ |
当我们改变了 this.message 时,会调用 nextTick,最终更新 dom。如果以同步访问的形式是拿不到变更后的 dom 的。所以新开一个 nextTick 来做 dom 更新之后的操作。
参考
- https://vuejs.org/v2/guide/reactivity.html#Async-Update-Queue
- https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
ISSU
有问题?来 GitHub 一起讨论。