watchEffect 是 Vue3 响应式系统的核心 API 之一,它自动追踪依赖并在依赖变化时重新执行回调函数。下面从核心用法和原理两个维度进行解析:
import { watchEffect, ref } from 'vue'
const count = ref(0)
// 自动追踪依赖,当 count 变化时重新执行
watchEffect(() => {
console.log(`count is: ${count.value}`)
})
watchEffect((onCleanup) => {
const timer = setInterval(() => {
console.log('tick')
}, 1000)
// 清理函数:在下一次执行前或停止监听时调用
onCleanup(() => clearInterval(timer))
})
watchEffect(
() => { /* ... */ },
{
flush: 'post', // 'pre' | 'post' | 'sync'
onTrack(e) { // 调试:追踪依赖时触发
debugger
},
onTrigger(e) { // 调试:依赖变化触发重新执行时
debugger
}
}
)
const stop = watchEffect(() => { /* ... */ })
// 手动停止
stop()
// 简化版原理示意
function watchEffect(effect) {
let cleanup
const reactiveEffect = new ReactiveEffect(() => {
// 执行清理函数
if (cleanup) cleanup()
// 开启依赖收集
effectTrackDepth++
activeEffect = reactiveEffect
// 执行用户回调,自动收集依赖
const result = effect(onCleanup => {
cleanup = onCleanup
})
// 结束收集
effectTrackDepth--
activeEffect = undefined
return result
})
reactiveEffect.run()
return () => reactiveEffect.stop()
}
执行流程:
1. 创建 ReactiveEffect 实例
2. 执行 effect.run()
3. → 触发 track() 开始依赖收集
4. → 执行用户回调,访问响应式数据
5. → 响应式数据的 getter 触发,将当前 effect 添加到依赖集合
6. → 回调执行完毕,结束收集
7. → 响应式数据变化时触发 trigger()
8. → 找到对应的 effect 并重新执行
| 特性 | watchEffect | watch |
|---|---|---|
| 依赖收集 | 自动 | 显式指定 |
| 初始执行 | 立即执行 | 可配置 |
| 访问新旧值 | 不支持 | 支持 |
| 使用场景 | 副作用聚合 | 特定响应式源变化 |
// 场景1:自动依赖多个数据
watchEffect(() => {
fetchData(query.value, filter.value)
})
// 场景2:DOM 操作(使用 flush: 'post')
watchEffect(() => {
if (isOpen.value) {
nextTick(() => inputRef.value?.focus())
}
}, { flush: 'post' })
// ❌ 避免异步回调中的依赖追踪
watchEffect(async () => {
// await 之后的响应式访问不会被追踪!
const data = await fetch(url)
console.log(someRef.value) // 不会被追踪
})
// ✅ 正确的异步用法
watchEffect(() => {
const id = userId.value // 同步收集依赖
fetchUser(id).then(user => {
// 这里可以访问,但不会建立响应式关联
})
})
// 使用响应式对象而非多个 ref
const filters = reactive({
keyword: '',
status: 'active',
page: 1
})
// 单个 watchEffect 替代多个 watch
watchEffect(() => {
search(filters.keyword, filters.status, filters.page)
})
watchEffect 体现了 Vue3 响应式系统的设计哲学:自动追踪 + 显式控制,既提供了便利性,又通过 API 设计避免了过度“魔法”。