标准答案

  1. Vue 修改响应式数据后,不会立刻同步更新 DOM,而是把组件更新任务放进异步队列,在当前同步代码执行完后统一刷新。
  2. nextTick 返回一个 Promise,等 Vue 完成本轮 DOM 更新后再继续执行。需要在改完状态后读取最新 DOM、滚动到新元素、测量高度时可以使用它。
  3. 异步更新队列会合并同一轮里的多次状态修改,避免每改一次数据就重新渲染一次组件。
  4. watch 的 flush 影响回调时机。默认 pre 通常在组件 DOM 更新前执行,post 在 DOM 更新后执行,sync 会同步触发。
  5. 如果只是依赖数据计算,不需要 nextTick。只有代码确实要读取更新后的 DOM 或第三方 DOM 状态时,才应该等待 nextTick。

题目解析

这题要把“数据更新”和“DOM 更新”分开。响应式数据会立即变,页面 DOM 会等 Vue 调度刷新。

nextTick 不是让数据生效,数据已经生效了;它等待的是 DOM 补上这次渲染结果。

flush: post 和 nextTick 经常一起被问。post 更适合 watch 内部读取更新后的 DOM,nextTick 更适合在普通逻辑里等待视图刷新。

代码示例

新增一条消息后,需要等 DOM 出现新节点,再把容器滚动到底部。

Vue
<script setup lang="ts">
import { nextTick, ref } from 'vue'

const messages = ref<string[]>([])
const listRef = ref<HTMLElement | null>(null)

async function addMessage(text: string) {
  messages.value.push(text)

  await nextTick()

  listRef.value?.scrollTo({
    top: listRef.value.scrollHeight
  })
}
</script>

<template>
  <ul ref="listRef">
    <li v-for="message in messages" :key="message">
      {{ message }}
    </li>
  </ul>
</template>

常见误区

  • 以为 nextTick 是让响应式数据更新。数据修改后已经变了,nextTick 等的是 DOM 更新。
  • 每次状态修改都 await nextTick,导致逻辑被切碎,性能和可读性都变差。
  • watch 里需要读取更新后的 DOM,却忘记使用 flush: "post" 或 nextTick。

作者信息