Vue 3 watchEffect 和 watch 的区别

FreeGuideOnline 最新 2026-07-06

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 注册清理函数
停止监听 返回停止函数 返回停止函数
选项参数 deepimmediateflush 仅支持 flush 配置(通过第二个参数传入)

1. 依赖收集方式

watchEffect 像“自动挡”,你只管写代码,它自己发现依赖了什么;watch 像“手动挡”,你必须告诉它要观察哪些数据。这使得 watchEffect 在多个分散的响应式状态联合触发副作用时非常方便。

2. 初始执行与旧值

如果你需要比较新旧值(如做动画、判断变化趋势),只能用 watchwatchEffect 更适合“需要保持最新状态”的场景,比如基于多个响应式值更新 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')
    }
  }
)