Vue3中watch和watchEffect的使用场景和区别
目录
watch
场景一:监听单个或多个特定数据的变化并执行副作用
场景二:监听多个数据源
watchEffect
场景一:自动追踪依赖并执行副作用
场景二:初始化时立即执行副作用
区别
监听方式
回调触发时机
响应式数据追踪方式
深度监听
减少watch中深度监听的性能开销的优化方法
精简被监听的数据结构
优化回调函数逻辑
精准监听
在 Vue 3 中,watch 和 watchEffect 都是用于响应式数据监听的方法,但它们的使用场景和工作方式不同。
watch
场景一:监听单个或多个特定数据的变化并执行副作用
当你需要精确地监听某个响应式数据(如 ref 或 reactive 中的某个属性)的变化,并在变化时执行一些特定的操作时,watch 非常有用。
例如,监听用户登录状态的变化,当登录状态改变时,更新页面的导航栏显示或者发起一些网络请求。
<template>
<div>
<input v-model="searchQuery" placeholder="Search">
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
export default defineComponent({
setup() {
const searchQuery = ref('');
watch(searchQuery, (newValue, oldValue) => {
console.log(`Search query changed from ${oldValue} to ${newValue}`);
// 这里可以发起搜索请求
});
return {
searchQuery
};
}
});
</script>
场景二:监听多个数据源
watch 也可以监听多个响应式数据的变化。例如,在一个购物车应用中,同时监听商品数量和商品价格的变化,来更新总价。
<template>
<div>
<input v-model="quantity" type="number">
<input v-model="price" type="number">
<p>Total: {
{ totalPrice }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';
export default defineComponent({
setup() {
const quantity = ref(1);
const price = ref(10);
const totalPrice = ref(0);
watch([quantity, price], ([newQuantity, newPrice], [oldQuantity, oldPrice]) => {
totalPrice.value = newQuantity * newPrice;
});
return {
quantity,
price,
totalPrice
};
}
});
</script>
watchEffect
场景一:自动追踪依赖并执行副作用
watchEffect 适合于当你有一个副作用函数,它依赖于多个响应式数据,并且你希望每当这些依赖中的任何一个发生变化时,该函数自动重新执行。
例如,在一个实时数据展示应用中,有多个数据来源影响某个图表的显示,使用 watchEffect 可以方便地实现图表的自动更新。
<template>
<div>
<input v-model="count" type="number">
<input v-model="factor" type="number">
<p>Result: {
{ result }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue';
export default defineComponent({
setup() {
const count = ref(1);
const factor = ref(2);
const result = ref(0);
watchEffect(() => {
result.value = count.value * factor.value;
});
return {
count,
factor,
result
};
}
});
</script>
场景二:初始化时立即执行副作用
watchEffect 会在创建时立即执行一次回调函数,这在需要在组件初始化时就执行一些与响应式数据相关的操作时很有用。比如,初始化一个地图组件,其初始位置依赖于一些响应式的坐标数据。
区别
监听方式
- watch:显式地指定要监听的数据源,可以是单个响应式数据(如 ref)或多个响应式数据组成的数组。它需要明确指出要监听什么。
- watchEffect:自动追踪回调函数中使用的所有响应式数据作为依赖,不需要显式指定要监听的数据源。只要回调函数中使用的响应式数据发生变化,它就会重新执行。
回调触发时机
watch
的回调函数只有在被监听的数据源发生变化时才会触发。它会传入新值和旧值两个参数,方便你对比数据变化前后的状态。watchEffect
的回调函数在组件初始化时会立即执行一次,以建立依赖关系。之后,每当依赖的响应式数据发生变化时,回调函数都会重新执行。
响应式数据追踪方式
watch
设置deep: true
时主要针对手动指定的监听源进行深度对比。watchEffect
是基于依赖追踪,只要在回调中使用到了响应式数据,无论层次多深,Vue 都会处理其变化通知。
深度监听
watch中深度监听:
- 使用 deep 选项(针对 reactive 数据),可以通过设置 deep: true 选项来开启深度监听
- 监听 ref 类型的对象或数组(深度监听),对于 ref 类型的对象或数组,watch 会自动进行深度监听,不需要额外设置 deep 选项
深度监听会消耗更多的性能,因为 Vue 需要递归地遍历对象的所有属性来检测变化。所以,建议只有在确实需要深度监听时才使用这种方式。
性能消耗原因:
- 深度监听原理:当开启深度监听(对于 reactive 创建的数据通过设置 deep: true)时,Vue 需要递归地遍历被监听对象的所有属性,为每一个属性都设置响应式追踪。这意味着在对象属性较多且层级较深的情况下,会有大量的属性需要处理。随着对象结构的复杂程度增加,遍历和追踪的工作量呈指数级增长。
- 频繁触发回调:深度监听会对对象内部的每一个属性变化都进行检测,只要任何一个深层属性发生变化,都会触发 watch 的回调函数。如果在短时间内有多个深层属性连续变化,回调函数可能会被频繁调用。频繁调用回调函数会带来额外的性能开销,特别是当回调函数内部有复杂的逻辑(如大量计算、DOM 操作、网络请求等)时,这种性能影响会更加明显。
- 内存占用:深度监听会让 Vue 的响应式系统维护更多的内部状态,包括依赖关系的记录等。这会占用更多的内存空间,对于内存资源有限的应用场景(如在移动设备上运行的应用),可能会导致性能问题,甚至出现内存溢出的风险
减少watch中深度监听的性能开销的优化方法
精简被监听的数据结构
减少嵌套层级:尽量简化需要深度监听的对象结构,减少不必要的嵌套层级。例如,如果你的数据结构中有一些深层嵌套且很少变化的部分,可以考虑将其提取出来,不放在深度监听的对象范围内。
按需拆分对象:对于大型复杂对象,可以根据变化频率和业务逻辑,将其拆分成多个较小的对象。只对那些需要深度监听且变化频繁的部分进行深度监听,其他部分单独处理。
优化回调函数逻辑
避免复杂计算:确保 watch 回调函数内部不进行复杂的计算操作。如果有复杂计算需求,可以将计算逻辑提取到独立的函数中,并在需要时调用,而不是每次回调都执行。
<template>
<div>
<button @click="updateData">Update Data</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, watch } from 'vue';
export default defineComponent({
setup() {
const data = reactive({
nested: {
value: 'initial'
}
});
const complexCalculation = () => {
// 复杂计算逻辑,例如大数据量的数组处理
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
return result;
};
watch(
() => data.nested,
(newValue, oldValue) => {
// 这里只执行必要的操作,避免复杂计算
console.log('Nested value changed:', newValue, oldValue);
// 如果确实需要复杂计算结果,在必要时调用
const calculationResult = complexCalculation();
console.log('Calculation result:', calculationResult);
},
{
deep: true
}
);
const updateData = () => {
data.nested.value = 'updated';
};
return {
data,
updateData
};
}
});
</script>
防抖和节流
如果 watch
回调函数中执行的操作(如网络请求、DOM 操作)不希望被频繁触发,可以使用防抖或节流技术。可以使用 lodash
库中的 debounce
方法、 throttle
方法
import { debounce } from 'lodash';
watch(
() => data.nested,
debounce((newValue, oldValue) => {
// 这里执行网络请求或其他不希望频繁触发的操作
console.log('Nested value changed (debounced):', newValue, oldValue);
}, 300),
{
deep: true
}
);
import { throttle } from 'lodash';
watch(
() => data.nested,
throttle((newValue, oldValue) => {
// 这里执行网络请求或其他不希望频繁触发的操作
console.log('Nested value changed (throttled):', newValue, oldValue);
}, 300),
{
deep: true
}
);
精准监听
只监听关键属性,不要对整个对象进行深度监听,而是通过计算属性或方法,只监听对象中真正需要关注的关键属性。
若碰到其他的问题 可以私信我 一起探讨学习
如果对你有所帮助还请 点赞
收藏 谢谢~!
关注收藏博客 持续更新中