Vue 3 提供了三种侦听响应式数据变化的手段,它们在触发时机、依赖收集方式、使用场景上各有侧重。合理选用可以让副作用逻辑更清晰、更可控。
watch:精确监听,拿到新旧值
watch 是最传统的侦听器,你需要显式指定要监听的数据源,并在回调中获取新值和旧值。它默认是懒执行的(数据变化后才触发),但可以通过 immediate: true 让回调在创建时立即执行一次。
<script setup>
import { ref, watch } from 'vue'
const keyword = ref('')
const page = ref(1)
// 监听单个数据源
watch(keyword, (newVal, oldVal) => {
console.log(`搜索词从 "${oldVal}" 变为 "${newVal}"`)
fetchData()
})
// 监听多个数据源(数组)
watch([keyword, page], ([newKw, newPage], [oldKw, oldPage]) => {
fetchData(newKw, newPage)
}, { immediate: true }) // 页面加载就立刻请求一次
</script>
适用场景:
- 需要 新/旧值对比 的逻辑(如表单撤销、版本差异比较)。
- 精确控制 “监听哪些数据” 而非自动收集依赖。
- 需要 深度监听对象内部变化 (
deep: true) 或 懒执行。 - 需要 手动停止监听 或 刷新时机控制(如
flush: 'post'延迟到 DOM 更新后执行)。
watchEffect:自动追踪依赖,立即执行
watchEffect 不需要你显式声明监听源,它会自动追踪回调中使用到的所有响应式数据,并在任意一个依赖变化时重新执行。它会在创建时立即执行一次,用来建立起初始依赖关系。
<script setup>
import { ref, watchEffect } from 'vue'
const userId = ref(1)
const userInfo = ref(null)
// 自动收集依赖,立即执行
watchEffect(async () => {
// 回调中使用了 userId.value,它会被自动追踪
const data = await fetchUser(userId.value)
userInfo.value = data
})
// 换一个用户,watchEffect 自动重新执行
function nextUser() {
userId.value++
}
</script>
适用场景:
- 副作用 没有新旧值需求,只关心“当前状态是什么”。
- 多个依赖存在 高度耦合:比如根据多个响应式变量计算出一个结果,一旦任一变量变就重新计算,用
watch需要手动列出所有依赖,而watchEffect自动完成。 - 初始化即执行 的副作用(省去
immediate: true)。 - 适合 日志记录、分析上报、同步派生状态 等不需要关心时序的场景。
watchPostEffect:DOM 更新后再执行
watchPostEffect 是 watchEffect 的一个别名,通过 flush: 'post' 选项实现,确保副作用在组件 DOM 更新完成后触发。此时可以安全地访问更新后的 DOM 元素。
<script setup>
import { ref, watchPostEffect } from 'vue'
const list = ref([])
const containerRef = ref(null)
// 每次 list 变化后,页面重新渲染完成再计算高度
watchPostEffect(() => {
console.log('容器高度:', containerRef.value?.scrollHeight)
})
</script>
适用场景:
- 副作用依赖 更新后的 DOM(如测量元素尺寸、滚动位置、触发动画)。
- 需要在 模板渲染全部完成 后才执行的操作。
- 与
watchEffect相比,延迟执行可以避免非必要的重复计算(因为可能在一次同步更新中执行多次 watchEffect,但最终 DOM 只更新一次)。
三种侦听器对比速查
| 特性 | watch | watchEffect | watchPostEffect |
|------|-------|-------------|------------------|
| 依赖收集 | 手动指定数据源 | 自动收集回调中使用到的响应式值 | 同 watchEffect,但触发时机在 DOM 更新后 |
| 是否立即执行 | 默认否,可配置 immediate | 是,创建时立即执行 | 是,但首次执行也在 DOM 挂载后 |
| 新旧值 | ✅ 提供 newVal / oldVal | ❌ 不提供 | ❌ 不提供 |
| 执行时机(flush) | 默认 pre(组件更新前),可通过选项改为 post | 默认 pre | 固定 post(DOM 更新后) |
| 典型场景 | 需要旧值对比、手动控制、深度监听 | 自动追踪、初始化执行、无旧值需求的副作用 | 操作 DOM 或需要在渲染完成后执行 |
选择建议:
- 需要 新/旧值对比 或 懒执行 →
watch - 只需 自动跟随最新状态 且 立即执行 →
watchEffect - 副作用要 碰 DOM 或依赖渲染结果 →
watchPostEffect(或watchEffect+flush: 'post')
这三种侦听器可以混用,根据副作用的实际需求灵活挑选,避免把一切都塞进 watch 里手动维护依赖列表。