watch
在 Vue 3 的 Composition API 中,watch
和 watchEffect
是用于响应式侦听数据变化的核心 API。它们都能追踪依赖并执行副作用,但在使用方式和场景上有显著差异。以下是详细解析:
watch
和 watchEffect
对比
特性 | watch | watchEffect |
---|---|---|
依赖收集方式 | 显式指定侦听的数据源 | 自动收集回调函数内的响应式依赖 |
立即执行 | 默认不立即执行(可通过 immediate: true 开启) | 总是立即执行 |
旧值访问 | 可获取旧值(oldValue ) | 无法获取旧值 |
适用场景 | 需要精确控制侦听目标 | 依赖多个数据或需要自动追踪依赖的场景 |
watch
的深度解析
1. 基本语法
import { watch, ref } from 'vue';
const count = ref(0);
// 监听单个 ref
watch(count, (newVal, oldVal) => {
console.log(`count变化:${oldVal} → ${newVal}`);
});
// 监听多个源(数组形式)
watch([count, otherRef], ([newCount, newOther], [oldCount, oldOther]) => {
// 处理多个变化
});
2. 监听不同类型的数据源
-
Ref 类型:
const num = ref(0); watch(num, (newVal) => { /* ... */ });
-
Reactive 对象属性:
const state = reactive({ user: { name: 'Alice' } }); watch( () => state.user.name, // 使用 getter 函数 (newName) => { /* ... */ } );
-
深层监听对象/数组:
watch( () => state.user, (newUser) => { /* ... */ }, { deep: true } // 深度监听嵌套属性变化 );
3. 配置选项
选项 | 说明 |
---|---|
deep: true | 深度监听对象/数组的内部变化 |
immediate: true | 立即触发回调(类似 Vue 2 的 immediate ) |
`flush: ‘pre’ | ‘post’ |
watch(
source,
callback,
{ deep: true, immediate: true, flush: 'post' }
);
watchEffect
的深度解析
1. 基本用法
import { watchEffect, ref } from 'vue';
const count = ref(0);
// 自动追踪依赖
watchEffect(() => {
console.log(`count的值:${count.value}`);
});
count.value = 1; // 输出 "count的值:1"
2. 特点
- 自动依赖追踪:回调函数中使用的响应式变量会被自动追踪。
- 立即执行:初始化时立即运行一次。
- 无需指定依赖:适合依赖多个响应式变量的场景。
3. 停止侦听器
通过调用返回的函数停止侦听:
const stop = watchEffect(() => { /* ... */ });
stop(); // 手动停止侦听
4. 清理副作用
在回调中返回一个清理函数,用于重置操作(如取消请求):
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
// 异步操作
}, 1000);
onCleanup(() => clearTimeout(timer)); // 清理函数
});
使用场景对比
1. 使用 watch
的场景
- 需要精确控制侦听的目标。
- 需要获取旧值进行比较。
- 需要延迟执行(默认不立即执行)。
- 需要深度监听对象/数组。
2. 使用 watchEffect
的场景
- 依赖多个响应式变量且不想逐个指定。
- 需要立即执行回调(如初始化加载数据)。
- 逻辑简单且依赖关系动态变化。
最佳实践与注意事项
1. 避免无限循环
在回调中修改被侦听的源数据可能导致无限循环:
// ❌ 错误示例
watch(count, (newVal) => {
count.value = newVal * 2; // 触发再次侦听
});
2. 性能优化
- 减少深度监听:
deep: true
对性能有影响,尽量明确侦听具体属性。 - 合理使用
flush
:flush: 'post'
可确保回调在 DOM 更新后执行。
3. 异步操作处理
-
使用
onCleanup
清理未完成的异步任务(如请求取消):watchEffect(async (onCleanup) => { const abortController = new AbortController(); onCleanup(() => abortController.abort()); // 清理时取消请求 const data = await fetch(url, { signal: abortController.signal }); });
完整示例
示例 1:监听搜索关键词(防抖)
<script setup>
import { ref, watch } from 'vue';
const searchKeyword = ref('');
let timer = null;
watch(searchKeyword, (newKeyword) => {
clearTimeout(timer);
timer = setTimeout(() => {
fetchResults(newKeyword); // 防抖处理
}, 500);
});
</script>
示例 2:自动追踪窗口尺寸
<script setup>
import { watchEffect } from 'vue';
const windowWidth = ref(window.innerWidth);
watchEffect((onCleanup) => {
const handleResize = () => {
windowWidth.value = window.innerWidth;
};
window.addEventListener('resize', handleResize);
onCleanup(() => {
window.removeEventListener('resize', handleResize);
});
});
</script>
API | 核心选择依据 |
---|---|
watch | 需要明确侦听特定数据源,获取旧值,或需要延迟执行、深度监听时使用 |
watchEffect | 依赖多个动态数据源,需要立即执行,或依赖关系复杂时使用 |