在 Vue 3 中,watch 和 watchEffect 都是用于响应式地监听数据变化的工具,但它们有不同的使用场景和工作机制。
1. watch
1、概念
watch 是 Vue 3 提供的一个用于观察响应式数据变化并在数据发生变化时执行特定操作的工具。它通常用于执行副作用,例如异步操作、手动更新 DOM、数据同步等。
2、用法
<template>
<div>
<p>count:{{ count }}</p>
<p>num:{{ state.num }}</p>
</div>
</template>
<script setup>
import { ref, watch ,reactive } from 'vue'
const count = ref(0);
// 写法一
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 写法二
watch(() => count.value, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
const state = reactive({ num: 0 })
watch(
() => state.num,
(newValue, oldValue) => {
console.log(`num changed from ${oldValue} to ${newValue}`);
},
);
window.count = count;
window.state = state;
</script>
3、特性
1)明确依赖:watch 需要明确指定监听的响应式数据,可以是 ref、reactive 对象的属性,甚至是一个返回响应式数据的函数。
2)旧值和新值:watch 回调函数的参数包括新值和旧值,在数据变化时进行后续操作很方便。
3)异步操作:watch 常用于监听数据变化并触发异步操作,如 API 请求、延迟处理等。
4)深度监听:通过设置 deep: true,watch 可以对嵌套对象或数组进行深度监听。
⚠️:深度监听需要递归遍历对象或数组的每一个属性。这种情况下,可能导致性能问题,尤其是在大型应用中。
<template>
<div>
<p>state.nested.count:{{ state.nested.count }}</p>
</div>
</template>
import { reactive, watch } from 'vue'
const state = reactive({
nested: {
count: 0
}
})
watch(
() => state.nested,
(newValue, oldValue) => {
console.log('nested count changed:', newValue.count)
},
{ deep: true }
)
window.state = state;
若是不添加 deep:true,则只是单纯的修改数据,而不执行 watch。
注意⚠️
state.nested 是一个响应式对象,当使用 watch 监听时,Vue 实际上是监测对象引用的变化。在 JS 中,newValue 和 oldValue 都是对同一个对象的引用。由于 reactive 对象是通过代理实现的,这意味着无论 newValue 还是 oldValue 都指向同一个代理对象,因此二者是相同的。
2. watchEffect
1、概念
watchEffect 是 Vue 3 引入的一种更为自动化的监听机制。它在函数内部自动收集依赖,任何响应式数据的变化都会触发这个函数重新执行。watchEffect 更加简洁,适用于无需明确指定监听数据源的场景。
2、用法
watchEffect 接收一个函数,这个函数会立即执行一次,并且在函数中访问到的所有响应式数据发生变化时再次执行。
<template>
<div>
<p>count:{{ count }}</p>
</div>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0);
watchEffect(() => {
console.log(`count is now: ${count.value}`)
})
window.count = count;
</script>
watchEffect 监听的数据多次调用时,会存在缓存。
import { reactive, ref, watchEffect } from "vue";
const state = reactive({ a: 1, b: 2 });
const countRef = ref(0);
watchEffect(() => {
console.log('state.a:', state.a, 'countRef.value:', countRef.value);
});
state.a++;
state.a++;
state.a++;
countRef.value++;
countRef.value++;
countRef.value++;
3、特性
1)自动依赖收集:watchEffect 不需要显式声明依赖项,它会自动收集函数内部使用的所有响应式数据并进行监听。
2)立即执行:watchEffect 在注册时立即执行一次,而不需要等待数据发生变化。
3)无需旧值对比:与 watch 不同,watchEffect 不会提供新旧值的对比,因为它主要用于自动处理副作用,而不是跟踪数据变化的前后差异。
4)停止侦听:watchEffect 返回一个停止侦听的函数,调用可以停止 effect 的依赖追踪。
const stop = watchEffect(() => {
console.log(`count is now: ${count.value}`);
});
stop(); // 调用后将停止 watchEffect 的监听
3. 联系与区别
1、联系
两者都是基于 Vue 3 响应式系统的监听工具,都可以用于在数据变化时执行副作用。
2、区别
1)依赖声明方式
- watch 需要手动声明依赖的数据,可以明确指定监听的数据,适合复杂场景。
- watchEffect 自动收集依赖,不需要手动指定,适合简单的、与 UI 强相关的场景。
2)执行时机
- watch 在数据发生变化时才执行,当在 options 中配置 immediate:true,函数在最开始立即执行。
- watchEffect 在注册时立即执行一次,然后在依赖变化时再次执行。
import { ref, watch } from 'vue'
const count = ref(0)
watch(
() => count.value,
(newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
},
{
immediate: true
}
)
count.value++;
3)处理复杂性
- watch 提供了更复杂的功能,如访问旧值、新值、深度监听、监听多个数据源等,适合需要精细控制的场景。
- watchEffect 简洁高效,适合简单的逻辑和不需要复杂控制的场景。
4. 实际开发中的使用建议
1、在简单的数据响应和副作用处理中,优先考虑使用 watchEffect,因为它更加简洁和自动化。
2、如果需要更精细的控制,比如对比新旧数据、深度监听对象、处理异步操作等,则使用 watch。
无论是 watchEffect 还是 watch,当依赖项发生变化时,回调函数的运行都是异步的,存放在微队列中。