vue3.x教程-nextTick() 延缓执行

提出问题

下面的代码,为什么执行 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) // 新值
  })  
}

原理

执行流程:

  1. 修改响应式变量 name.value
  2. Vue 将 DOM 更新任务推入微任务队列(不立即执行)
  3. 继续执行当前同步代码(此时 DOM 未变化)
  4. 同步代码执行结束
  5. 执行微任务:批量更新真实 DOM
  6. DOM 更新完成后,执行 nextTick() 的回调(如果有多个nextTick(),都会执行)

队列机制特点

  1. 多次修改数据只会触发一次 DOM 更新
state.a = 1
state.a = 2
state.a = 3
// Vue只会执行1次DOM渲染,优化性能
  1. nextTick 的回调会挂载在DOM 更新之后的微任务中

Vue3 nextTick 内部异步策略(优先级)

Vue3 内部自动降级选择异步方案:

  1. 优先使用 Promise.then(微任务)
  2. 不支持 Promise 环境 → MutationObserver
  3. 再降级 → 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 时才使用。

总结

  1. Vue 数据更新 → DOM 异步延迟刷新;
  2. nextTick:注册回调,等待本轮 DOM 更新完成后运行;
  3. 适用:改数据后立刻读取 / 操作最新 DOM;
  4. Vue3 优先使用 await nextTick();
  5. 本质利用浏览器微任务实现,批量渲染优化性能。

原文出处:/show_1GW3kSSBcD9l.html