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