flush 是 Vue3 中控制副作用函数执行时机的配置选项,用于决定响应式数据变化后,副作用(watch、watchEffect、组件渲染)在何时执行

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' ❌ 不批处理 极少场景(调试、特殊需求)

核心要点总结

  1. flush 控制副作用执行时机:决定在组件渲染前、渲染后还是立即执行

  2. 默认使用 flush: 'pre':性能最优,适合大多数场景

  3. 操作 DOM 时使用 flush: 'post':确保能获取到更新后的 DOM 元素

  4. 避免使用 flush: 'sync':会失去 Vue 的批处理优化,可能影响性能

  5. 与 nextTick 的区别

    • flush: 'post':在组件更新后、nextTick 之前执行

    • nextTick:在所有 flush 回调执行完成后才执行

  6. 组件渲染也使用相同的刷新机制:Vue 组件的更新队列也遵循相同的 flush 时机,保证了整个响应式系统的一致性


nextTick 是 Vue 提供的全局 API,用于在下一次 DOM 更新完成后执行回调函数

相关推荐
拾贰_C2 小时前
【Vue | vue3 | spring boot】前端前台项目搭建
前端·vue.js·spring boot
蓝黑20202 小时前
Vue SFC Playground
前端·javascript·vue.js
qq_406176142 小时前
React与Vue异同点及优缺点深度解析
前端·vue.js·react.js
SuperEugene10 小时前
Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇
开发语言·前端·javascript·vue.js·前端框架·axios
英俊潇洒美少年12 小时前
vue如何实现react useDeferredvalue和useTransition的效果
前端·vue.js·react.js
英俊潇洒美少年13 小时前
ref 底层到底是怎么变成响应式的?
vue.js
英俊潇洒美少年13 小时前
react19和vue3的优缺点 对比
前端·javascript·vue.js·react.js
多看书少吃饭15 小时前
Vue + Java + Python 打造企业级 AI 知识库与任务分发系统(RAG架构全解析)
java·vue.js·笔记
SuperEugene16 小时前
Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇
前端·javascript·vue.js·前端框架·axios