Vue
nextTick
Vue 的响应式更新是异步批量的——修改数据后,DOM 不会立刻变化,而是等当前同步代码执行完毕,再统一更新。nextTick(fn) 让你在 DOM 更新完成后执行回调。
问题场景
<template>
<div v-if="visible" ref="panel">Hello</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const visible = ref(false)
const panel = ref(null)
function showPanel() {
visible.value = true
console.log(panel.value) // ❌ null,DOM 还没更新
nextTick(() => {
console.log(panel.value) // ✅ <div> 元素
})
}
</script>也可以用 async/await 写法:
async function showPanel() {
visible.value = true
await nextTick()
console.log(panel.value) // ✅ <div> 元素
}为什么 DOM 不立刻更新?
Vue 采用异步批量更新策略,避免同一帧内多次修改数据触发多次渲染:
count.value = 1
count.value = 2
count.value = 3
// 只触发一次 DOM 更新,而不是三次更新流程:
- 修改响应式数据 → Vue 将组件更新任务推入更新队列
- 当前同步代码继续执行(不阻塞)
- 同步代码执行完毕,微任务阶段统一 flush 更新队列,执行 DOM patch
nextTick回调在 DOM patch 之后执行
nextTick 的实现原理
nextTick 本质是把回调注册到微任务队列,确保在 DOM 更新后执行。简化实现如下:
const callbacks: (() => void)[] = []
let waiting = false
function nextTick(cb: () => void) {
callbacks.push(cb)
if (!waiting) {
waiting = true
Promise.resolve().then(() => {
waiting = false
callbacks.forEach(fn => fn())
callbacks.length = 0
})
}
}调用 nextTick(cb) 时,cb 被推入队列;第一次调用还会安排一个 Promise.then 微任务。当前同步代码全部跑完后,微任务阶段统一执行所有回调。
执行顺序:
同步阶段
├── visible.value = true → 组件更新任务入队
└── nextTick(cb) → cb 入队,注册 Promise.then 微任务
微任务阶段(Promise.then)
├── flush 更新队列 → 执行 DOM patch(v-if 挂载 div)
└── flush nextTick 队列 → 执行 cb,此时 panel.value 已是 div 元素Vue 2 vs Vue 3
| 对比项 | Vue 2 | Vue 3 |
|---|---|---|
| 主要实现 | Promise.then → MutationObserver → setImmediate → setTimeout 降级 | 固定使用 Promise.then |
| 兼容性处理 | 有多层降级逻辑,兼容旧浏览器 | 依赖现代浏览器 Promise,代码更简洁 |
| 可预测性 | 降级路径多,行为在不同环境略有差异 | 统一微任务,行为一致 |