️ 副作用的自动巡航:深入理解 Vue 3 的 watchEffect
在上一节我们详细探讨了 watch,它如同一位狙击手 ,精准、冷静,需要你明确指定目标(数据源)和回调逻辑。而在 Vue 3 的响应式工具箱中,还有另一位得力干将------watchEffect。
如果说 watch 是"命令式"的,那么 watchEffect 则是"反应式"的。它更像是一台自动巡航雷达,你只需要定义好"当数据变化时要做什么",它就会自动扫描并追踪代码中依赖了哪些响应式数据,并在它们变化时重新执行。
什么是 watchEffect?
watchEffect 的核心定义是:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
它的最大特点在于自动依赖收集 。你不需要像 watch 那样手动传入 ref 或写 getter 函数,watchEffect 会在首次执行时,"触碰"所有用到的响应式属性,建立依赖关系。
基础语法:
import { watchEffect } from 'vue'
watchEffect((onCleanup) => {
// 副作用逻辑
// onCleanup 是用于注册清理回调的函数
})
️ 核心特性:自动与立即
watchEffect 的行为与 watch 有着本质的区别,主要体现在以下两点:
-
自动追踪(Automatic Dependency Tracking)
<script setup> import { ref, watchEffect } from 'vue'
在watchEffect的回调函数中,你直接使用响应式数据即可。Vue 会在回调执行时自动记录你访问了哪些属性,这些属性就成为了它的依赖。const url = ref('/api/users')
const data = ref(null)// 不需要显式指定监听 url
// 只要 url.value 在函数内被使用,就会自动被追踪
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})// 当 url.value 改变时,上面的请求会自动重新执行
</script> -
立即执行(Eager Evaluation)
watchEffect在创建时会立刻同步执行一次 回调函数。这意味着你不需要配置immediate: true,它天生就是立即执行的。这在初始化数据请求或同步 DOM 状态时非常有用。
副作用清理:onCleanup
这是 watchEffect 中最精妙的设计之一。在异步操作中(如防抖、定时器、未完成的 API 请求),旧的副作用往往需要被取消,否则会导致竞态条件(Race Condition)或内存泄漏。
watchEffect 接收一个函数作为参数,该函数提供了一个 onCleanup 方法。你可以在其中注册清理函数,它会在以下时机被调用:
- 下一次副作用重新执行之前。
- 组件卸载时(自动清理)。
实战示例:防抖搜索与定时器清理
<script setup>
import { ref, watchEffect } from 'vue'
const searchQuery = ref('')
const results = ref([])
// 场景 1:防抖处理
watchEffect((onCleanup) => {
// 模拟防抖
const timer = setTimeout(() => {
console.log('执行搜索:', searchQuery.value)
// 模拟请求
// results.value = fetch(...)
}, 500)
// 清理逻辑:如果 searchQuery 迅速变化,
// 上一个 setTimeout 还没执行,就会先触发这里的清理
onCleanup(() => {
clearTimeout(timer)
console.log('清理上一次的定时器')
})
})
// 场景 2:长轮询或定时任务
const isActive = ref(true)
watchEffect((onCleanup) => {
if (!isActive.value) return
const interval = setInterval(() => {
console.log('轮询中...')
}, 3000)
// 组件卸载或 isActive 变为 false 时,自动清除定时器
onCleanup(() => clearInterval(interval))
})
</script>
️ 高级配置:flush 与 onTrack/onTrigger
虽然 watchEffect 的默认行为是"自动"的,但我们依然可以通过第二个参数对其进行微调。
-
控制执行时机 (
flush)
默认情况下,watchEffect是在组件更新之前 ('pre')同步执行的。如果你需要在 DOM 更新之后 操作 DOM,可以将其设置为'post'。watchEffect(() => {
// 这里的代码会在 DOM 更新后执行
// 适合操作渲染后的 DOM 元素
}, {
flush: 'post'
}) -
调试依赖 (
onTrack** &onTrigger)**
这两个钩子主要用于调试。onTrack在依赖被收集时触发,onTrigger在回调被触发时触发。你可以利用它们打印出是哪个数据导致了副作用的执行。watchEffect(() => {
console.log(count.value)
}, {
onTrack(e) {
console.log('追踪了依赖:', e)
},
onTrigger(e) {
console.log('触发了回调:', e)
}
})
️ watch 与 watchEffect:如何抉择?
在文章的最后,我们通过一个场景化的对比,来总结何时该用谁:
| 场景 | 推荐 API | 理由 |
|---|---|---|
| 需要明确知道"什么变了" | watch |
watch 显式指定数据源,逻辑更清晰,适合处理特定业务逻辑。 |
| 需要获取"旧值"与"新值" | watch |
watch 回调直接提供新/旧值参数,对比状态变迁更方便。 |
| 发起异步请求或防抖 | watchEffect |
利用 onCleanup 能完美处理竞态和清理,代码更简洁。 |
| 同步状态到外部系统 | watchEffect |
例如同步状态到 localStorage、同步 props 到内部状态,自动追踪非常高效。 |
| 复杂的计算逻辑 | computed |
如果只是求值,不要用 watchEffect,用 computed。 |
一句话总结:
当你的逻辑是"只要这些数据变了,我就重新做这件事 ",且逻辑较为简单直接时,优先选择 watchEffect;当你的逻辑是"当那个特定的数据发生某种变化时,我要执行这个复杂的副作用 ",请使用 watch。