watcheffect 默认在 dom 更新前执行,需设 flush: 'post' 才能在渲染后操作真实 dom;'pre' 读旧 dom,'sync' 破坏批量更新,'post' 适用于聚焦、滚动、测量等场景。

watchEffect 默认在组件更新前执行,也就是 DOM 还没刷新时回调就已运行。要让它等 DOM 渲染完再执行,关键就是配置 flush 选项。
flush 的三种取值和实际效果
它控制副作用函数与 Vue 渲染周期的相对顺序,不是延时或定时,而是协调执行阶段:
-
flush: 'pre'(默认):数据变化后、DOM 更新前触发。此时
ref.value是新值,但document.querySelector拿到的仍是旧 DOM 状态。 -
flush: 'post':数据变化后、Vue 完成 DOM 更新、进入微任务队列时触发。这时能安全调用
focus()、getBoundingClientRect()或读取渲染后的元素尺寸。 - flush: 'sync':数据一变立刻同步执行,跳过队列缓冲。会破坏批量更新优化,容易引发重复执行或性能问题,仅限调试或极特殊逻辑。
什么时候必须用 flush: 'post'
当你在 watchEffect 里需要操作真实 DOM 或依赖布局信息时,'pre' 会失败,'sync' 不稳定,'post' 是可靠选择:
- 自动聚焦刚渲染出来的输入框(
inputRef.value?.focus()) - 滚动到新插入的列表项(
el.scrollIntoView({ block: 'nearest' })) - 测量容器高度后调整图表 canvas 尺寸
- 依赖
offsetHeight或clientWidth做条件判断
常见误区与验证方式
不要以为加了 nextTick 就能替代 flush: 'post' —— 两者语义不同:nextTick 是通用异步钩子,而 flush: 'post' 是 watchEffect 生命周期的一部分,天然与该侦听器绑定,且只在依赖变化时触发。
验证是否生效,可在回调里打印:
- 当前响应式值(如
count.value)→ 总是最新 - DOM 元素文本内容(如
el.textContent)→ 'pre' 下是旧值,'post' 下是新值
搭配 onInvalidate 做清理更稳妥
尤其在使用 flush: 'post' 执行 DOM 操作时,若下一次副作用还没执行、组件就卸载了,可能造成内存泄漏或报错。这时利用 onInvalidate 注册清理逻辑:
- 取消未完成的
setTimeout或fetch请求 - 移除手动添加的事件监听器
- 重置临时 class 或样式
失效触发时机包括:依赖再次变化、watchEffect 被 stop、组件 unmounted。










