Vue3中的flush选项控制副作用函数的执行时机,提供三种模式:'pre'(默认,组件更新前执行)、'post'(组件更新后执行)和'sync'(立即同步执行)。
'pre'适合获取更新前DOM状态,'post'用于操作更新后DOM,而'sync'可能影响性能应谨慎使用。
该机制与组件渲染队列协同工作,'pre'和'post'支持批处理优化,而'sync'会破坏这种优化。
实际开发中应根据需求选择合适模式,通常优先使用'pre',DOM操作场景选用'post',仅在特殊情况下考虑'sync'。
flush 是什么
flush 是 Vue3 中控制副作用函数执行时机的配置选项,用于决定响应式数据变化后,副作用(watch、watchEffect、组件渲染)在何时执行。
flush 的三种取值
| 取值 | 执行时机 | 说明 | 适用场景 |
|---|---|---|---|
'pre' |
组件更新前执行 | 默认值,在 DOM 更新之前执行副作用 | 需要获取更新前的 DOM 状态 避免不必要的重复渲染 |
'post' |
组件更新后执行 | 在 DOM 更新之后执行副作用 | 需要操作更新后的 DOM 获取最终渲染结果 |
'sync' |
同步立即执行 | 数据变化时立即同步执行,不进行缓冲 | 少数特殊场景 (一般不推荐,可能影响性能) |
执行时机详解
1. flush: 'pre'(默认)
数据变化后,副作用会在组件更新前执行。
html
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const element = ref(null)
watchEffect(() => {
console.log('count:', count.value)
// 此时 DOM 还未更新
console.log('DOM内容:', element.value?.textContent)
}, { flush: 'pre' })
// 修改数据
count.value++
// 执行顺序:
// 1. 数据修改
// 2. 执行 watchEffect 回调(flush: 'pre')
// 3. 组件重新渲染,更新 DOM
</script>
特点:
-
✅ 可以获取更新前的 DOM 状态
-
✅ 避免不必要的 DOM 操作
-
⚠️ 无法获取最新的 DOM
2. flush: 'post'
数据变化后,副作用会在组件更新后执行。
html
<script setup>
import { ref, watchEffect, nextTick } from 'vue'
const count = ref(0)
const element = ref(null)
watchEffect(() => {
console.log('count:', count.value)
// 此时 DOM 已经更新
console.log('DOM内容:', element.value?.textContent)
}, { flush: 'post' })
// 修改数据
count.value++
// 执行顺序:
// 1. 数据修改
// 2. 组件重新渲染,更新 DOM
// 3. 执行 watchEffect 回调(flush: 'post')
</script>
特点:
-
✅ 可以获取更新后的 DOM
-
✅ 适合操作 DOM 的场景
-
⚠️ 比 'pre' 稍晚执行
典型应用场景:
html
<script setup>
import { ref, watchEffect } from 'vue'
const message = ref('Hello')
const messageRef = ref(null)
// 需要操作更新后的 DOM
watchEffect(() => {
// 确保 DOM 已更新,可以获取元素高度、滚动位置等
if (messageRef.value) {
const height = messageRef.value.offsetHeight
console.log('消息高度:', height)
}
}, { flush: 'post' })
</script>
<template>
<div ref="messageRef">{{ message }}</div>
</template>
3. flush: 'sync'
数据变化时立即同步执行副作用,不进行任何缓冲或批处理。
html
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => {
console.log('count:', count.value)
}, { flush: 'sync' })
console.log('1. 初始执行')
count.value++
console.log('2. 修改数据后')
count.value++
console.log('3. 再次修改')
// 输出顺序:
// 1. 初始执行
// count: 0
// 2. 修改数据后
// count: 1 ← 同步立即执行
// 3. 再次修改
// count: 2 ← 同步立即执行
</script>
特点:
-
✅ 最精确的执行时机
-
⚠️ 可能影响性能(无法批处理更新)
-
⚠️ 可能导致重复执行或死循环
-
⚠️ 一般不推荐使用(除非特殊需求)
完整对比示例
html
<script setup>
import { ref, watch, nextTick } from 'vue'
const count = ref(0)
const logs = ref([])
function addLog(msg) {
logs.value.push(`${new Date().toLocaleTimeString()} - ${msg}`)
}
// flush: 'pre' - 组件更新前执行
watch(count, (newVal, oldVal) => {
addLog(`pre: ${oldVal} → ${newVal},DOM未更新`)
}, { flush: 'pre' })
// flush: 'post' - 组件更新后执行
watch(count, (newVal, oldVal) => {
addLog(`post: ${oldVal} → ${newVal},DOM已更新`)
}, { flush: 'post' })
// flush: 'sync' - 同步执行
watch(count, (newVal, oldVal) => {
addLog(`sync: ${oldVal} → ${newVal},立即执行`)
}, { flush: 'sync' })
// 修改数据
count.value++
nextTick(() => {
addLog('--- nextTick 执行 ---')
})
// 可能的输出顺序(简化):
// sync: 0 → 1,立即执行
// pre: 0 → 1,DOM未更新
// post: 0 → 1,DOM已更新
// --- nextTick 执行 ---
</script>
执行时机对比图
html
数据变化 (count.value++)
│
├─ flush: 'sync' → 立即执行副作用
│
├─ 收集所有待执行的副作用('pre' 和 'post')
│
├─ 执行所有 flush: 'pre' 的副作用
│
├─ 组件重新渲染(更新 DOM)
│
├─ 执行所有 flush: 'post' 的副作用
│
└─ nextTick 回调执行
实际应用场景
场景1:使用 flush: 'post' 操作 DOM
html
<script setup>
import { ref, watchEffect } from 'vue'
const todoList = ref([])
const listRef = ref(null)
// 每次列表更新后,自动滚动到底部
watchEffect(() => {
if (todoList.value.length > 0 && listRef.value) {
// flush: 'post' 确保 DOM 已更新,可以访问元素高度
listRef.value.scrollTop = listRef.value.scrollHeight
}
}, { flush: 'post' })
// 添加新待办事项
function addTodo(text) {
todoList.value.push({ id: Date.now(), text })
}
</script>
<template>
<div ref="listRef" class="todo-list">
<div v-for="todo in todoList" :key="todo.id">
{{ todo.text }}
</div>
</div>
</template>
场景2:使用 flush: 'pre' 避免闪烁
html
<script setup>
import { ref, watch } from 'vue'
const isLoading = ref(false)
const loadingText = ref('加载中...')
// 在 DOM 更新前修改加载文本,避免显示旧内容
watch(isLoading, (newVal) => {
if (newVal) {
loadingText.value = '数据加载中,请稍候...'
}
}, { flush: 'pre' })
</script>
场景3:使用 flush: 'sync' 调试(谨慎使用)
html
<script setup>
import { ref, watch } from 'vue'
const state = ref(0)
// 仅用于调试,确保每次修改都能立即看到日志
watch(state, (newVal) => {
console.log('状态变化:', newVal)
}, { flush: 'sync' })
// 多次修改会触发多次输出(无批处理)
state.value = 1 // 输出:状态变化: 1
state.value = 2 // 输出:状态变化: 2
state.value = 3 // 输出:状态变化: 3
</script>
性能影响对比
| flush 类型 | 批处理 | 性能 | 适用性 |
|---|---|---|---|
| 'pre' | ✅ 会批处理 | 高 | 大多数场景 |
| 'post' | ✅ 会批处理 | 高 | 需要 DOM 操作的场景 |
| 'sync' | ❌ 不批处理 | 低 | 极少场景(调试、特殊需求) |
核心要点总结
-
flush 控制副作用执行时机:决定在组件渲染前、渲染后还是立即执行
-
默认使用
flush: 'pre':性能最优,适合大多数场景 -
操作 DOM 时使用
flush: 'post':确保能获取到更新后的 DOM 元素 -
避免使用
flush: 'sync':会失去 Vue 的批处理优化,可能影响性能 -
与 nextTick 的区别:
-
flush: 'post':在组件更新后、nextTick 之前执行 -
nextTick:在所有 flush 回调执行完成后才执行
-
-
组件渲染也使用相同的刷新机制:Vue 组件的更新队列也遵循相同的 flush 时机,保证了整个响应式系统的一致性