Vue 3 watchEffect 和 watch 的区别
javascript import { ref, watch } from 'vue'
const count = ref(0)
// 监听单个 ref
watch(count, (newVal, oldVal) => {
console.log(count 从 ${oldVal} 变成 ${newVal})
})
// 监听 getter 函数
watch(
() => count.value + 1,
(newVal, oldVal) => {
console.log(计算结果变化: ${oldVal} -> ${newVal})
}
)
// 监听多个源
const name = ref('Vue')
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(count: ${oldCount}->${newCount}, name: ${oldName}->${newName})
})
**关键特性:**
- **惰性执行**:回调默认不会在初始化时立即执行,只有依赖变化才会触发。可通过 `{ immediate: true }` 改为立即执行。
- **可获取旧值**:回调参数始终提供 `newValue` 和 `oldValue`。
- **可配置选项**:`deep`(深度监听)、`immediate`(立即执行)、`flush`(回调时机)等。
- **精准控制**:你可以选择只监听特定属性,避免不必要的副作用。
---
## watchEffect 的核心用法
`watchEffect` 会自动收集回调内部用到的所有响应式状态,首次运行并建立依赖追踪,之后任何一个依赖变化都会重新执行回调。
```javascript
import { ref, watchEffect } from 'vue'
const count = ref(0)
const doubled = ref(0)
// 自动追踪 count.value 和 doubled.value
const stop = watchEffect(() => {
console.log(`count: ${count.value}, doubled: ${doubled.value}`)
})
count.value++ // 触发回调
doubled.value = count.value * 2 // 再次触发回调
关键特性:
- 立即执行:回调在
watchEffect创建时立刻运行一次,以便收集依赖。 - 无法获取旧值:回调只提供当前最新的值,不提供旧值。
- 自动依赖收集:无需手动声明依赖,代码更简洁。
- 副作用清理:支持
onCleanup注册清理函数,适合处理异步操作或取消请求。
核心区别对比
| 特性 | watch | watchEffect |
|---|---|---|
| 依赖声明 | 显式指定数据源(ref、getter 等) | 自动收集回调内使用的响应式状态 |
| 执行时机 | 默认惰性,依赖变化时执行(可配置 immediate) | 立即执行一次,之后依赖变化再次执行 |
| 访问旧值 | 回调参数提供 (newVal, oldVal) |
无法直接获取旧值 |
| 适用场景 | 需要精确监测特定数据变化,需要比较新旧值 | 多个依赖联动、需要自动副作用的场景 |
| 清理副作用 | 回调中手动处理,无内置清理机制 | 通过 onCleanup 注册清理函数 |
| 停止监听 | 返回停止函数 | 返回停止函数 |
| 选项参数 | deep、immediate、flush 等 |
仅支持 flush 配置(通过第二个参数传入) |
1. 依赖收集方式
watchEffect 像“自动挡”,你只管写代码,它自己发现依赖了什么;watch 像“手动挡”,你必须告诉它要观察哪些数据。这使得 watchEffect 在多个分散的响应式状态联合触发副作用时非常方便。
2. 初始执行与旧值
如果你需要比较新旧值(如做动画、判断变化趋势),只能用 watch。watchEffect 更适合“需要保持最新状态”的场景,比如基于多个响应式值更新 DOM 或执行拉取操作。
3. 副作用清理
watchEffect 内置清理机制,常用于取消异步任务:
watchEffect((onCleanup) => {
const controller = new AbortController()
fetch(url, { signal: controller.signal }).then(/*...*/)
onCleanup(() => controller.abort())
})
watch 则需自行处理清理,通常借助返回的停止函数或在回调内部判断。
该用哪个?场景决策指南
优先使用 watchEffect 的情况
- 副作用依赖于多个响应式状态,且这些依赖随代码逻辑自然出现。
- 你需要立即执行副作用,并根据最新状态持续更新。
- 你希望代码更简洁,不想手动列出所有依赖。
- 需要自动清理异步任务(如请求防抖、轮询)。
示例:将用户 ID 和配置响应式地拼装成 URL 并发起请求
watchEffect(async (onCleanup) => {
const url = `/api/user/${userId.value}?format=${config.format}`
const controller = new AbortController()
onCleanup(() => controller.abort())
const data = await fetch(url, { signal: controller.signal }).then(r => r.json())
userData.value = data
})
优先使用 watch 的情况
- 你需要精确知道哪个具体数据发生了变化,并获取它的旧值。
- 你希望控制回调是惰性执行还是立即执行(通过
immediate)。 - 监听的是某一个特定的值,且逻辑需要根据新旧值做判断。
- 需要深度监听对象或数组内部变化(
deep: true)。
示例:监听路由参数变化,并判断前进/后退
watch(
() => route.params.id,
(newId, oldId) => {
if (newId !== oldId) {
transitionTo(newId, oldId ? 'back' : 'forward')
}
}
)