---
URL: "/zh-CN/blog/2025-10/vue-watch.html"
LLMS_URL: "/zh-CN/blog/2025-10/vue-watch.md"
lastUpdated: true
commentabled: true
recommended: true
title: "Vue 侦听器（watch）详解"
description: "监听数据的变化"
date:

pageClass: "blog-page-class"
cover: "/covers/vue.svg"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: Vue 侦听器（watch）详解
description: 监听数据的变化
date: 2025-10-14 10:30:00 
pageClass: blog-page-class
cover: /covers/vue.svg
---

在 Vue 3 中，watch() 是组合式 API 的重要成员，用于 *响应式地监听数据变化并触发副作用*。虽然我们大多数人都用过它，但你是否真正了解它的高阶用法？是否遇到过这些问题：

- 为什么 watch 的回调会在组件挂载时就触发？

- 如何深度监听一个嵌套对象的变化？

- 如何同时监听多个数据源？

- 如何控制监听回调的时机？

- 如何手动停止监听？

这篇文章将带你*由浅入深*地掌握 `watch()` 的高级用法，彻底告别“用得了但搞不清”。

## 🧩 基础回顾：watch 是干嘛的？ ##

watch 就是用来 *侦听响应式数据的变化*，当被监听的值变化时，执行副作用逻辑。

```js
import { ref, watch } from 'vue'

const count = ref(0)

watch(count, (newVal, oldVal) => {
  console.log(`count: ${oldVal} → ${newVal}`)
})
```

当你运行 `count.value++` 时，会触发 `watch` 的回调函数。

## 📘 监听多个数据源（数组监听） ##

有时你需要同时监听多个变量，使用数组即可：

```js
const a = ref(1)
const b = ref(2)

watch([a, b], ([newA, newB], [oldA, oldB]) => {
  console.log(`a: ${oldA} → ${newA}`)
  console.log(`b: ${oldB} → ${newB}`)
})
```

注意：监听数组时，*参数是数组结构的 [newValArray, oldValArray]* ，不是单个值。

## 🔍 深度监听对象（deep watch） ##

当监听对象或数组时，默认是 *浅监听*，只监听引用变化：

```js
const obj = ref({ name: 'Vue', age: 3 })

watch(obj, (newVal) => {
  console.log('对象整体变了')
})
```

如果只是 `obj.value.age++`，不会触发 `watch`。解决办法是开启 `deep: true`：

```js
watch(obj, (newVal) => {
  console.log('深度监听：属性变了', newVal)
}, { deep: true })
```

> 注意：监听响应式对象最好不要监听 `ref(obj)` 而是监听 `obj.value` 内部字段或使用 `reactive()` 结合 `deep`。

## 🕹 控制监听回调触发时机（immediate） ##

默认情况下，watch 只在依赖变化时触发。使用 `immediate: true` 可以在 *初次监听时就执行一次回调*：

```js
watch(count, (newVal, oldVal) => {
  console.log('首次立即执行', newVal)
}, { immediate: true })
```

**常用于**：

- 初始化时拉取接口数据；
- 初始化日志打印；
- 需要拿到默认值执行逻辑。

## ⏰ 监听对象某个字段变化（函数监听） ##

你可以监听一个 `getter` 函数，*精准指定你要监听的响应式属性*：

```js
const user = ref({ name: 'Alice', age: 25 })

watch(() => user.value.age, (newAge) => {
  console.log('年龄变了', newAge)
})
```

这样可以避免 `deep` 带来的性能消耗，非常推荐在实际开发中使用。

## 🔄 监听 reactive 对象的任意字段变化 ##

如果你用的是 `reactive()`，无法直接 `watch` 某个属性。你有两种选择：

### ✅ 方法一：使用 `getter` ###

```js
const user = reactive({ name: 'Bob', age: 30 })

watch(() => user.age, (newVal) => {
  console.log('age 改变了', newVal)
})
```

### ✅ 方法二：直接 `deep` 监听整个对象 ###

```javascript
watch(user, (newVal) => {
  console.log('任意字段变了', newVal)
}, { deep: true })
```

但注意：*`deep` 有性能开销，尽量精确监听具体字段*。

## 🧼 watch 的清理函数（副作用释放） ##

当你在 `watch` 内部使用异步操作或副作用逻辑，比如定时器或网络请求，需要在下次触发之前清理：

```js
watch(count, (newVal, oldVal, onCleanup) => {
  const timer = setTimeout(() => {
    console.log('模拟异步请求完成')
  }, 1000)

  onCleanup(() => {
    clearTimeout(timer)
    console.log('上一次副作用被清除')
  })
})
```

这可以防止旧请求/副作用残留，避免竞态条件、内存泄漏等问题。

## ❌ 如何停止监听？ ##

`watch` 会返回一个停止监听的函数，你可以在某些条件下主动调用：

```js
const stop = watch(count, (val) => {
  console.log('监听中', val)
  if (val > 5) {
    stop()
    console.log('监听已停止')
  }
})
```

## 🧠 最佳实践建议 ##

- ✅ 精准监听字段（使用 `getter`）而非整个对象；
- ✅ `deep: true` 慎用，有性能开销；
- ✅ 需要初始化调用就加 `immediate: true`；
- ✅ 有异步副作用时配合 `onCleanup` 清理；
- ✅ 大多数时候优先使用 `computed`，`watch` 用于“有副作用”的场景。

## 🧪 实战演练：用户表单自动保存 ##

```js
const form = reactive({
  name: '',
  email: ''
})

watch(
  () => ({ ...form }), // 简单实现浅层结构监听
  (val) => {
    console.log('自动保存', val)
    saveToLocalStorage(val)
  },
  { deep: true, immediate: true }
)

function saveToLocalStorage(data) {
  localStorage.setItem('form-data', JSON.stringify(data))
}
```

## 🎯 总结 ##

|  场景   |      推荐做法  |
| :-----------: | :-----------: |
| 监听单个 ref | 直接传变量 |
| 监听多个数据源 | 传数组 |
| 监听某个字段 | 传函数 |
| 初始即执行 | `{ immediate: true }` |
| 对象内部变动 | `{ deep: true }`或监听字段 |
| 清理副作用 | 使用 `onCleanup` |
| 手动停止 | 调用 `stop()` |

## 🪄 最后 ##

`watch()` 是 Vue 响应式系统中不可或缺的一部分，很多场景都离不开它，比如接口请求、缓存逻辑、UI 控制等。

希望这篇文章能帮你彻底掌握 watch 的各种高阶用法，*写出更健壮、更高效的 Vue 组件*。
