Vue中的nextTick
Vue 在批量更新 DOM 后等待视图完成刷新的 nextTick 机制及其使用场景。
#tech / dev / frame
#resource / vue3
#type / concept
#status / growing
[!info] related notes
- 所属 MOC: Vue MOC
- 上位主题: Vue 响应式系统, vue3
- 相关机制: vue-render-and-update-flow, vue3-virtual-dom
Vue中的nextTick
一句话定义
nextTick 返回一个 Promise,在 Vue 完成本轮 DOM 更新后才执行回调,用于在状态变更后读取最新 DOM。
核心机制
为什么需要 nextTick
Vue 的响应式更新是异步批量的。当你修改状态时,Vue 不会立刻更新 DOM,而是把更新推入一个异步队列,在同一个事件循环 tick 中的多次状态变更会被合并为一次 DOM 更新。
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(0)
function increment() {
count.value++
// 此时 DOM 还没有更新
console.log(document.getElementById('counter').textContent) // "0"
}
</script>
<template>
<div id="counter">{{ count }}</div>
<button @click="increment">+1</button>
</template>
nextTick 的底层机制
Vue 3 的 nextTick 内部优先使用 Promise.resolve() 创建微任务:
// 简化实现
const resolvedPromise = Promise.resolve()
function nextTick(fn) {
return fn ? resolvedPromise.then(fn) : resolvedPromise
}
这意味着 nextTick 的回调在当前同步代码执行完毕、微任务队列开始处理时执行,正好排在 Vue 的 DOM 更新之后。
微任务队列时序
同步代码执行
↓
count.value++ → 推入更新队列
↓
console.log(DOM) → 读到旧 DOM(更新还没执行)
↓
同步代码结束
↓
微任务队列开始
↓
Vue 执行 DOM 更新(flush jobs)
↓
nextTick 回调执行 → 读到新 DOM
代码示例
基本用法
<script setup>
import { ref, nextTick } from 'vue'
const message = ref('旧消息')
const el = ref(null)
async function update() {
message.value = '新消息'
// DOM 还没更新
console.log(el.value.textContent) // "旧消息"
await nextTick()
// DOM 已更新
console.log(el.value.textContent) // "新消息"
}
</script>
<template>
<div ref="el">{{ message }}</div>
<button @click="update">更新</button>
</template>
更新列表后滚动到底部
<script setup>
import { ref, nextTick } from 'vue'
const messages = ref([])
const listEl = ref(null)
async function addMessage(text) {
messages.value.push(text)
await nextTick()
listEl.value.scrollTop = listEl.value.scrollHeight
}
</script>
打开弹窗后聚焦输入框
<script setup>
import { ref, nextTick } from 'vue'
const visible = ref(false)
const inputEl = ref(null)
async function openDialog() {
visible.value = true
await nextTick()
inputEl.value.focus()
}
</script>
nextTick 与 setTimeout 的区别
nextTick 是微任务,在 Vue 更新队列 flush 之后立即执行。setTimeout 是宏任务,排在所有微任务之后,时机更晚且不精确。需要等 DOM 更新时,始终用 nextTick。
边界与常见误解
- nextTick 不是”万能延迟”:它只等一轮 DOM 更新,不是
setTimeout的替代品。 - nextTick 的回调在微任务队列中执行:如果在 nextTick 回调中再次修改状态,新的更新会在下一轮微任务中处理。
- 多个 nextTick 调用会合并:连续调用
nextTick(fn1)和nextTick(fn2),fn1 和 fn2 都会在同一轮 DOM 更新后执行。 - Vue 2 中 nextTick 是全局方法和实例方法:
this.$nextTick()和Vue.nextTick()。Vue 3 中推荐使用导入的nextTick()函数。 - watchEffect / watch 的 flush 选项:
watchEffect(fn, { flush: 'post' })等价于在 DOM 更新后执行,可以替代部分 nextTick 场景。
面试要点
来自 vue-next-tick-interview-question 的面试视角整理。
一句话回答
nextTick 用来等待 Vue 把本轮状态变化对应的 DOM 更新真正刷完,适合那些必须依赖最新 DOM 的场景。
最稳的回答主线
- Vue 会批量调度 DOM 更新,同一 tick 中的多次状态变更合并为一次更新
- 所以改完状态后,马上读 DOM 不一定是新结果
- 如果后续逻辑依赖最新 DOM,就用
nextTick - 底层基于
Promise.resolve()微任务,在 Vue 更新队列 flush 之后执行