watch 的作用就是:“请帮我盯着这个数据,当它变化时,就去执行这个函数。 ”
这在你需要根据数据变化来执行异步操作(如 API 请求)、设置 localStorage 或执行一些无法在 computed 中完成的复杂逻辑时非常有用。
1.watch基础用法
首先,你需要在 <script setup lang="ts"> 中导入 watch 和你想要侦听的响应式数据(如 ref 或 reactive)。
<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 可以通过一个数组同时侦听多个源(可以是 ref 和 getter 的混合体)。
<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 接受第三个参数,是一个选项对象,最常用的是 immediate 和 deep。
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. 核心使用场景总结
-
执行异步操作:当
props或ref变化时,去调用 API (如fetch)。 -
响应
props变化:在子组件中侦听props的变化,并执行内部逻辑。
const props = defineProps<{ userId: string }>();
watch(() => props.userId, (newId) => { ... });
- 操作浏览器 API:当数据变化时,将其存入
localStorage。
watch(
cartItems,
(newCart) => {
localStorage.setItem("cart", JSON.stringify(newCart));
},
{ deep: true }
);
-
需要旧值的复杂逻辑:当你需要比较新值和旧值来决定执行什么操作时。
watch和computed的关键区别是:computed:用于计算派生出一个新值,并且它不应该有副作用。watch:用于在数据变化时执行操作 (副作用) ,它不返回任何东西。