Vue 中 nextTick 的原理详解
1. 为什么需要 nextTick
Vue 采用 异步渲染机制,当响应式数据发生变化时,Vue 并不会立即更新 DOM,而是将这些变化放入一个 队列 中,并在 同一事件循环(Event Loop)中合并相同的修改,最后执行批量更新。这样做的目的是 提升性能,避免不必要的重复渲染。
例如:
<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: "Hello"
};
},
mounted() {
this.msg = "Vue";
console.log(document.querySelector("div").innerText); // 仍然是 "Hello"
this.$nextTick(() => {
console.log(document.querySelector("div").innerText); // 现在是 "Vue"
});
}
};
</script>
为什么 console.log
还是 "Hello"?
因为 Vue 在 this.msg = "Vue"
时 不会立即更新 DOM,而是等本轮事件循环结束后再更新。因此,我们需要 nextTick
来确保获取到更新后的 DOM。
2. nextTick
的原理
Vue 的 nextTick
本质上是一个 异步任务调度器,它会在当前 DOM 任务完成后执行回调。其内部原理主要依赖 微任务(Microtask)和 宏任务(Macrotask)。
2.1 任务队列
Vue 内部维护了一个 回调队列(callback queue),当 nextTick
被调用时,它会将回调函数 推入队列,然后等待 Vue 进行 DOM 更新后,再依次执行这些回调。
2.2 任务调度策略
nextTick
采用 优雅降级 的策略,在不同环境下选择最佳的异步方法:
- Promise(Microtask)(首选,现代浏览器支持)
- MutationObserver(Microtask)(比
setTimeout
更快) - setImmediate(Macrotask)(仅 IE 支持)
- setTimeout(Macrotask)(最后的兜底方案)
代码实现:
function nextTick(callback) {
const p = Promise.resolve();
p.then(callback);
}
在 Vue 3 中:
let callbacks = [];
let pending = false;
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let cb of copies) {
cb();
}
}
export function nextTick(cb) {
callbacks.push(cb);
if (!pending) {
pending = true;
Promise.resolve().then(flushCallbacks);
}
}
流程解析:
- 每次调用
nextTick(cb)
,将cb
放入callbacks
队列中。 - 只要
pending === false
,就启动 微任务(Promise.then)。 - 微任务执行
flushCallbacks
,依次调用callbacks
队列中的所有回调。
3. nextTick
在 Vue 2 和 Vue 3 的区别
3.1 Vue 2 的 nextTick
在 Vue 2 中,nextTick
主要依赖:
- Microtask(Promise.then, MutationObserver)
- Macrotask(setImmediate, setTimeout)
- 维护了一个 异步任务队列,用于批量执行
nextTick
回调。
3.2 Vue 3 的 nextTick
Vue 3 主要优化:
- 只使用 Promise 作为微任务(不再使用 MutationObserver)。
- 更高效的 异步队列处理机制。
Vue 3 中的 nextTick
:
const resolvedPromise = Promise.resolve();
export function nextTick(fn) {
return fn ? resolvedPromise.then(fn) : resolvedPromise;
}
优化点
- 直接使用
Promise.resolve().then(fn)
,避免了 Vue 2 复杂的回调队列管理。 - 如果不传入
fn
,则返回一个 Promise,支持await this.$nextTick()
。
4. nextTick
的使用场景
4.1 在 DOM 更新后执行操作
<template>
<div ref="box">{{ message }}</div>
</template>
<script>
export default {
data() {
return { message: "Hello" };
},
methods: {
updateMessage() {
this.message = "Vue";
this.$nextTick(() => {
console.log(this.$refs.box.innerText); // "Vue"
});
}
}
};
</script>
4.2 在 watch
中等待 DOM 更新
watch(() => state.count, async (newVal) => {
await nextTick();
console.log(document.querySelector("#counter").innerText); // 确保 DOM 已更新
});
4.3 在 Vue 3 setup
中使用
import { nextTick, ref } from "vue";
setup() {
const message = ref("Hello");
const updateMessage = async () => {
message.value = "Vue";
await nextTick();
console.log(document.querySelector("#msg").innerText);
};
return { message, updateMessage };
}
5. 总结
nextTick
是 Vue 提供的一个 异步任务调度方法,用于在 DOM 更新后执行回调。- Vue 采用 异步批量更新 机制,
nextTick
可确保 数据变更后获取到最新的 DOM。 - Vue 内部采用 Promise(Microtask)优先,降级到 MutationObserver / setTimeout 作为备用方案。
- Vue 3 进一步优化了
nextTick
,减少了不必要的复杂度,提升了性能。
你可以简单理解为:
Vue 在修改数据后,不会立即更新 DOM,而是 批量合并修改,并在下一次 事件循环(Event Loop)结束时更新 DOM。
nextTick
让你可以等到 DOM 更新完成后再执行操作。