Vue3-watch

36 阅读4分钟

watch 的作用就是:“请帮我盯着这个数据,当它变化时,就去执行这个函数。

这在你需要根据数据变化来执行异步操作(如 API 请求)、设置 localStorage 或执行一些无法在 computed 中完成的复杂逻辑时非常有用。

1.watch基础用法

首先,你需要在 <script setup lang="ts"> 中导入 watch 和你想要侦听的响应式数据(如 refreactive)。

<script setup lang="ts">
import { ref, watch } from 'vue';

const count = ref(0);

// 侦听 count
// 回调函数接收两个参数:新值 和 旧值
watch(count, (newValue: number, oldValue: number) => {
  console.log(`Count 从 ${oldValue} 变为 ${newValue}`);
});

// 模板中使用的示例
const increment = () => {
  count.value++;
};
</script>

<template>
  <p>Count: {{ count }}</p>
  <button @click="increment">增加</button>
</template>

2.侦听某个属性

当你不想直接侦听整个对象,而只想侦听对象上的某个特定属性时,这是最推荐的方式。你需要传入一个返回该属性的 getter 函数

<script setup lang="ts">
import { reactive, watch } from 'vue';

const state = reactive({
  id: 1,
  user: {
    name: 'Alice',
    age: 30
  }
});

// 侦听 state.id
watch(
  () => state.id, // 传入一个 getter 函数
  (newId: number, oldId: number) => {
    console.log(`ID 从 ${oldId} 变为 ${newId}`);
    // 场景:当 ID 变化时,去获取新用户的数据
    // fetchUser(newId);
  }
);
</script>

3.侦听多个源

watch 可以通过一个数组同时侦听多个源(可以是 refgetter 的混合体)。

<script setup lang="ts">
import { ref, reactive, watch } from 'vue';

const query = ref('');
const category = ref('all');

// 侦听 query 和 category
watch(
  [query, category], // 侦听源数组,多个数据源用数组包裹
  ([newQuery, newCategory], [oldQuery, oldCategory]) => {
    console.log(`Query: ${oldQuery} -> ${newQuery}`);
    console.log(`Category: ${oldCategory} -> ${newCategory}`);
    
    // 场景:当搜索词或分类变化时,重新获取列表
    // searchApi(newQuery, newCategory);
  }
);
</script>

4.侦听 Reactive 对象

你也可以直接侦听 reactive 对象,但这有一些重要的注意事项。

<script setup lang="ts">
import { reactive, watch } from 'vue';

const state = reactive({
  name: '',
  age: 0
});

// 1. 默认是“深度”侦听 (deep: true)
// 2. 注意:newState 和 oldState 是相同的!(它们都指向同一个代理对象)
watch(state, (newState, oldState) => {
  console.log('State 变化了', newState);
  
  // 警告:在这里 newState === oldState
  // 因为它们都是对同一个 reactive 代理的引用
  
  // 如果你需要获取旧值,你必须使用“getter”函数并返回一个新对象
  // 如下所示:
});

// ✅ 获取 oldState 的正确方式:
watch(
  () => ({ ...state }), // 使用 getter 返回一个 state 的浅拷贝
  (newState, oldState) => {
    // 现在 newState 和 oldState 是不同的对象
    console.log('旧年龄:', oldState.age, '新年龄:', newState.age);
  }
);
</script>

5.watch 的选项 (Options)

watch 接受第三个参数,是一个选项对象,最常用的是 immediatedeep

  • immediate: true (立即执行)

默认情况下,watch 只在侦听的源发生变化后才执行回调。设置 immediate: true 会强制回调在侦听器创建时立即执行一次

<script setup lang="ts">
import { ref, watch } from 'vue';

const userId = ref(101); // 假设从 props 或 store 获得

// 场景:我们希望页面加载时就根据 userId 获取一次数据,
// 之后 userId 变化时再获取
watch(
  userId,
  (newId) => {
    console.log(`正在获取 ID: ${newId} 的数据...`);
    // fetchUserData(newId);
  },
  { immediate: true } // 立即执行
);
</script>
  • deep: true (深度侦听)

当你侦听一个 ref,但它内部是一个嵌套对象时,watch 默认不会侦听该对象的深层属性变化。你需要 deep: true 来强制它这样做。(注意:侦听 reactive 对象时,deep: true 是默认开启的)

6.停止侦听

watch 会返回一个函数,调用该函数可以停止侦听。这在 onUnmounted 钩子中清理副作用时很有用。

<script setup lang="ts">
import { ref, watch, onUnmounted } from 'vue';

const count = ref(0);

const unwatch = watch(count, (newVal) => {
  console.log(newVal);
});

// 假设 5 秒后我们不再需要这个侦听器
setTimeout(() => {
  console.log('停止侦听 count');
  unwatch();
}, 5000);

// 在组件卸载时自动停止侦听
// (在 <script setup> 中,watch 会自动绑定到组件实例,
// 并在组件卸载时自动停止,所以手动 unwatch 并不总是需要)
// onUnmounted(() => {
//   unwatch();
// });
</script>

7. 核心使用场景总结

  1. 执行异步操作:当 propsref 变化时,去调用 API (如 fetch)。

  2. 响应 props 变化:在子组件中侦听 props 的变化,并执行内部逻辑。

const props = defineProps<{ userId: string }>();
watch(() => props.userId, (newId) => { ... });
  1. 操作浏览器 API:当数据变化时,将其存入 localStorage
watch(
  cartItems,
  (newCart) => {
    localStorage.setItem("cart", JSON.stringify(newCart));
  },
  { deep: true }
);
  1. 需要旧值的复杂逻辑:当你需要比较新值和旧值来决定执行什么操作时。

    watchcomputed 的关键区别是:

    • computed:用于计算派生出一个新值,并且它不应该有副作用
    • watch:用于在数据变化时执行操作 (副作用) ,它不返回任何东西。