Vue3 的 watch 是响应式编程中用于监听数据变化的核心 API,适用于执行副作用操作(如异步请求、复杂逻辑处理等)。本文将详细介绍其用法、配置项及实战场景,并通过示例代码帮助开发者快速掌握。


一、基础用法

1. 监听简单值(Ref)
通过直接传入 ref 对象,监听基本类型数据的变化:

import { ref, watch } from 'vue';
const count = ref(0);

watch(count, (newVal, oldVal) => {
  console.log(`值从 ${oldVal} 变为 ${newVal}`);
});

count.value++; // 触发回调:输出 "值从 0 变为 1"

2. 监听响应式对象(Reactive)
监听整个响应式对象时,需开启 deep 选项以捕获嵌套属性的变化:

const user = reactive({ name: 'Alice', age: 25 });

watch(user, (newVal) => {
  console.log('用户信息变化:', newVal);
}, { deep: true });

user.age = 26; // 触发回调

3. 监听对象特定属性
使用 Getter 函数 监听对象中的单个属性,避免不必要的深度遍历:

watch(
  () => user.name,
  (newName, oldName) => {
    console.log(`姓名从 ${oldName} 变为 ${newName}`);
  }
);

二、高级配置选项

1. 立即执行(immediate)
组件初始化时立即触发回调,常用于数据预加载:

watch(count, (newVal) => {
  fetchData(newVal); // 初始化时立即执行
}, { immediate: true });

2. 深度监听(deep)
强制监听对象内部所有属性变化(默认仅监听顶层属性):

watch(user, () => {
  // 响应所有嵌套属性的变化
}, { deep: true });

3. 执行时机(flush)
控制回调触发时机:

  • pre(默认):DOM 更新前执行
  • post:DOM 更新后执行
  • sync:同步执行(慎用,可能导致性能问题)
watch(source, callback, { flush: 'post' });

4. 单次监听(once)
Vue3.4+ 新增功能,回调仅执行一次:

watch(count, () => {
  console.log('仅触发一次');
}, { once: true });

三、进阶用法

1. 监听多个数据源
通过数组形式同时监听多个响应式变量:

const [firstName, lastName] = [ref('A'), ref('B')];

watch([firstName, lastName], ([newFirst, newLast]) => {
  console.log(`全名:${newFirst} ${newLast}`);
});

2. 处理异步操作
结合 onCleanup 清理过期请求,避免竞态问题:

watch(searchQuery, async (newVal, _, onCleanup) => {
  let isCancelled = false;
  onCleanup(() => (isCancelled = true)); // 清理前一次请求
  
  const res = await fetch(`/api?q=${newVal}`);
  if (!isCancelled) {
    updateResults(res.data);
  }
});

3. 停止侦听器
调用 watch 返回的停止函数,手动终止监听:

const stop = watch(count, () => { /* ... */ });
stop(); // 停止监听

四、注意事项

  1. 性能优化

    • 避免滥用 deep: true,尤其是大型对象
    • 优先使用计算属性处理派生数据
  2. 对象监听陷阱

    • 直接替换整个对象会触发回调,修改属性需依赖 deep 或 Getter 函数
  3. watchEffect 的区别

    • watch 需显式指定依赖,适合需要旧值的场景
    • watchEffect 自动追踪依赖,适合简单副作用

五、完整示例

<template>
  <div>
    <input v-model="name" placeholder="输入姓名">
    <input type="number" v-model="age">
    <p>历史记录: {{ logs }}</p>
  </div>
</template>

<script setup>
import { ref, reactive, watch } from 'vue';

const name = ref('');
const age = ref(0);
const user = reactive({ details: { role: 'user' } });
const logs = ref([]);

// 监听多个数据源
watch([name, age], ([newName, newAge]) => {
  logs.value.push(`姓名:${newName},年龄:${newAge}`);
});

// 深度监听嵌套对象
watch(
  () => user.details.role,
  (newRole) => {
    console.log('角色变更:', newRole);
  },
  { deep: true }
);
</script>