提出问题
下面的代码,为什么执行 name.value = '李雷' 后,获取 id="name" 的控件的值,还是空值?
<template>
<div>
<input id="name" v-model="name" />
<button @click="handle">修改</button>
</div>
</template>
<script setup>
import { nextTick,ref } from 'vue'
const name = ref('')
function handle() {
name.value = '李雷'
// 修改数据后,dom不会马上更新,所以获取不到值
console.log(document.querySelector('#name').value) // 旧值!DOM还没更新
}
</script>
分析
改数据后 → DOM 不会马上更新
解决
使用 nextTick() 延缓执行
介绍
Vue 在更新 DOM 时是异步执行的:
当响应式数据发生变更,Vue 不会立刻去更新 DOM,而是把这些 DOM 更新缓存到一个队列;等当前同步代码全部执行完毕之后,再统一处理队列、更新 DOM。
所以:修改数据后,立刻去获取 DOM,拿到的还是更新前的旧 DOM。nextTick() 的作用:等待下一次 DOM 更新完成后执行回调代码。
一句话解释
改数据后 → DOM 不会马上更新,nextTick () 让后面的代码延后执行,等 DOM 真正刷新完毕后,再执行。
使用
组合式 API
await、async
修改上面的 handle() 函数,如下:
async function handle() {
name.value = '李雷'
// 延缓后面的代码执行,等 DOM 真正刷新完毕后,再执行
await nextTick()
console.log(document.querySelector('#name').value) // 新值
}
回调方式
修改上面的 handle() 函数,如下:
function handle() {
name.value = '李雷'
// 等 DOM 真正刷新完毕后,再执行
nextTick(() => {
console.log(document.querySelector('#name').value) // 新值
})
}
原理
执行流程:
- 修改响应式变量
name.value - Vue 将 DOM 更新任务推入微任务队列(不立即执行)
- 继续执行当前同步代码(此时 DOM 未变化)
- 同步代码执行结束
- 执行微任务:批量更新真实 DOM
- DOM 更新完成后,执行
nextTick()的回调(如果有多个nextTick(),都会执行)
队列机制特点
- 多次修改数据只会触发一次 DOM 更新
state.a = 1
state.a = 2
state.a = 3
// Vue只会执行1次DOM渲染,优化性能
- nextTick 的回调会挂载在DOM 更新之后的微任务中
Vue3 nextTick 内部异步策略(优先级)
Vue3 内部自动降级选择异步方案:
- 优先使用
Promise.then(微任务) - 不支持 Promise 环境 →
MutationObserver - 再降级 →
setTimeout(宏任务)
绝大多数现代浏览器都是 Promise 微任务
典型使用场景
什么时候需要 nextTick?
修改响应式数据 → 紧接着要操作 DOM(获取元素、聚焦、滚动、读取元素大小)
满足这个流程,就要加 nextTick
场景 1:修改数据后,立刻获取 / 操作最新 DOM 元素
- 获取输入框
offsetHeight、元素尺寸 - 获取
ref绑定 DOM 节点
<template>
<div v-if="show" ref="box">内容</div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const show = ref(false)
const box = ref(null)
async function open() {
show.value = true
// 直接打印:box.value === null,DOM还没创建
await nextTick()
console.log(box.value) // 成功拿到真实DOM元素
}
</script>
场景 2:动态创建 DOM 后,操作第三方 DOM 库
如 echarts、富文本编辑器、拖拽库,需要 DOM 存在才能初始化
tableVisible.value = true
await nextTick()
echarts.init(dom) // 此时dom已经渲染完成
场景 3:v-if /v-show 切换后操作元素
v-if 会销毁重建 DOM,切换后必须 nextTick 等待节点挂载。
场景 4:滚动到新增列表项
请求追加列表数据,想自动滚动到最后一条
list.value.push(...newData)
await nextTick()
scrollToBottom()
易错点
❌ 错误:连续多个 nextTick 不要误以为顺序串行
nextTick 全部加入同一轮更新之后执行:
state.x = 1
nextTick(()=>console.log(1))
nextTick(()=>console.log(2))
// DOM只更新一次,两个回调连续执行
❌ 误区:nextTick 不能等待接口请求
nextTick 只管 DOM 渲染,不等待异步网络请求
async function demo(){
// 网络请求是独立异步,nextTick等不到接口返回
fetchData().then(res=> list.value = res)
await nextTick()
// 此时接口还没返回,list还没赋值!
}
❌ 不要滥用 nextTick
很多场景可以通过数据驱动解决,尽量避免直接操作 DOM,只有必须获取原生 DOM 时才使用。
总结
- Vue 数据更新 → DOM 异步延迟刷新;
- nextTick:注册回调,等待本轮 DOM 更新完成后运行;
- 适用:改数据后立刻读取 / 操作最新 DOM;
- Vue3 优先使用
await nextTick(); - 本质利用浏览器微任务实现,批量渲染优化性能。