'# Vue3如何使用watch和computed属性实现数据的变更监听和计算
一、背景与问题
在Vue3的响应式系统中,watch和computed是处理数据变更的核心工具。理解它们的原理和适用场景,是构建高效、可维护的Vue应用的关键。
1.1 问题场景
在开发过程中,我们常遇到以下问题:
- 需要根据数据变化动态计算衍生值
- 需要响应数据变化执行副作用操作
- 需要避免重复计算和不必要的DOM更新
1.2 传统方案的局限
在Vue2中,开发者常使用watch和computed,但Vue3的响应式系统基于Proxy实现,其底层原理与Vue2的Object.defineProperty有本质区别。理解这些差异是掌握Vue3响应式特性的关键。
二、基本原理
2.1 响应式系统的底层实现
Vue3通过createReactivity函数创建响应式系统,核心是Proxy和Reflect API:
// 简化版响应式系统
function createReactivity(target) {
return new Proxy(target, {
get: (obj, key) => {
// 依赖收集逻辑
return Reflect.get(obj, key);
},
set: (obj, key, value) => {
// 触发更新逻辑
return Reflect.set(obj, key, value);
}
});
}当访问属性时,会触发依赖收集;当属性变化时,会触发更新。
2.2 computed的实现原理
computed本质是创建了一个响应式计算属性:
function computed(fn) {
const result = ref();
const effect = createEffect(() => {
result.value = fn();
});
return result;
}通过createEffect创建的副作用函数会在依赖项变化时自动更新。
2.3 watch的实现原理
watch本质是创建一个副作用函数并附加到响应式依赖:
function watch(source, callback) {
const effect = createEffect(() => {
const value = source();
callback(value);
});
return effect;
}通过source()获取当前值,当依赖项变化时触发回调。
三、环境准备
3.1 项目创建
使用Vite创建Vue3项目:
npm create vue@latest3.2 开发环境
确保已安装以下依赖:
npm install vue@next四、核心实现
4.1 computed的使用场景
适用于需要计算衍生值且不直接修改数据的情况。
示例1:计算属性
<template>
<div>
原始价格: {{ price }} 元
折后价格: {{ discountedPrice }} 元
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const price = ref(100);
const discountRate = ref(0.8);
const discountedPrice = computed(() => {
return price.value * discountRate.value;
});
</script>关键代码解释:
computed创建一个响应式计算属性- 当
price或discountRate变化时,discountedPrice会自动更新 - 计算函数内部的变量访问会触发依赖收集
4.2 watch的使用场景
适用于需要执行副作用操作的情况,如API调用、数据持久化等。
示例2:监听数据变化
<template>
<div>
输入内容: <input v-model="inputValue" />
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const inputValue = ref('');
watch(
() => inputValue.value,
(newVal, oldVal) => {
console.log(`输入内容从 ${oldVal} 变为 ${newVal}`);
}
);
</script>关键代码解释:
watch监听inputValue的变化- 回调函数接收新旧值
- 适用于需要处理异步操作的场景
4.3 watchEffect的使用场景
适用于监听多个响应式依赖的场景。
示例3:自动计算
<template>
<div>
姓名: <input v-model="firstName" />
姓氏: <input v-model="lastName" />
全名: {{ fullName }}
</div>
</template>
<script setup>
import { ref, watchEffect, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
watchEffect(() => {
console.log(`全名更新为: ${fullName.value}`);
});
</script>关键代码解释:
watchEffect自动追踪所有响应式依赖- 当
firstName或lastName变化时触发回调 - 可用于自动执行副作用
五、完整案例
5.1 电商商品信息展示系统
5.1.1 功能需求
- 显示商品价格和折扣信息
- 实时计算折扣后价格
- 监听用户输入修改折扣率
- 监听价格变化触发库存更新
5.1.2 项目结构
src/
├── components/
│ └── ProductCard.vue
└── App.vue5.1.3 实现代码
App.vue
<template>
<div class="container">
<ProductCard
:price="price"
:discount-rate="discountRate"
@update:discount-rate="handleDiscountChange"
/>
<div class="info">
当前价格: {{ price }} 元
当前折扣率: {{ discountRate }} 折
折后价格: {{ discountedPrice }} 元
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import ProductCard from './components/ProductCard.vue';
const price = ref(100);
const discountRate = ref(0.8);
const discountedPrice = computed(() => {
return price.value * discountRate.value;
});
const handleDiscountChange = (newRate) => {
discountRate.value = newRate;
};
</script>ProductCard.vue
<template>
<div class="product-card">
<div class="input-group">
<label>价格 (元): </label>
<input
type="number"
v-model="price"
@input="handlePriceChange"
/>
</div>
<div class="input-group">
<label>折扣率 (0-1): </label>
<input
type="number"
v-model="discountRate"
@input="handleDiscountChange"
/>
</div>
<div class="action">
<button @click="applyDiscount">应用折扣</button>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const price = ref(100);
const discountRate = ref(0.8);
const emit = defineEmits(['update:discount-rate']);
const handlePriceChange = (event) => {
price.value = parseFloat(event.target.value) || 100;
};
const handleDiscountChange = (event) => {
discountRate.value = parseFloat(event.target.value) || 0.8;
};
const applyDiscount = () => {
emit('update:discount-rate', discountRate.value);
};
watch(() => discountRate.value, (newRate) => {
console.log(`折扣率更新为 ${newRate}`);
});
</script>六、源码解析
6.1 computed的实现原理
function computed(fn) {
const result = ref();
const effect = createEffect(() => {
result.value = fn();
});
return result;
}- 创建一个响应式计算属性
- 通过
createEffect创建副作用函数 - 当依赖项变化时自动更新
6.2 watch的实现原理
function watch(source, callback) {
const effect = createEffect(() => {
const value = source();
callback(value);
});
return effect;
}- 创建一个副作用函数并附加到响应式依赖
- 通过
source()获取当前值 - 当依赖项变化时触发回调
七、进阶使用
7.1 复杂依赖管理
在处理多个响应式依赖时,可以使用watchEffect:
watchEffect(() => {
console.log(`价格: ${price.value}, 折扣率: ${discountRate.value}`);
});7.2 深度监听
使用deep选项进行深度监听:
watch(
() => product,
(newVal, oldVal) => {
console.log('product changed', newVal);
},
{ deep: true }
);7.3 立即执行
使用immediate选项立即执行回调:
watch(
() => price.value,
(newVal, oldVal) => {
console.log(`价格从 ${oldVal} 变为 ${newVal}`);
},
{ immediate: true }
);八、性能与工程实践
8.1 性能优化
- 避免不必要的计算:在
computed中避免复杂的计算逻辑 - 使用
deep选项:对复杂对象使用深度监听 - 使用
immediate选项:立即执行回调 - 合理使用
watchEffect:自动追踪依赖项
8.2 异常处理
watch(
() => price.value,
(newVal, oldVal) => {
try {
// 可能抛出异常的计算逻辑
} catch (error) {
console.error('计算异常:', error);
}
}
);8.3 安全考量
- 防止XSS攻击:在模板中使用
v-html时要确保内容安全 - 数据校验:在
watch回调中进行输入校验 - 避免无限循环:在
computed中避免修改依赖项
九、常见问题与踩坑
9.1 常见错误
错误示例:
watch(price, (newVal) => { price.value = newVal * 0.8; // 会导致无限循环 });问题:直接修改依赖项会导致无限循环
解决:通过ref或reactive修改数据错误示例:
computed(() => { return someNonReactiveData; // 非响应式数据 });问题:计算属性依赖非响应式数据
解决:确保所有依赖项都是响应式的错误示例:
watch( () => price.value, (newVal) => { fetch('/api/update', { price: newVal }); // 未处理错误 } );问题:未处理网络错误
解决:添加错误处理逻辑
9.2 常见问题
| 问题 | 解决方案 |
|---|---|
| 计算属性未更新 | 检查依赖项是否为响应式 |
| watch未触发 | 确保依赖项是响应式对象 |
| 性能问题 | 使用deep选项和immediate选项 |
| 无限循环 | 避免直接修改依赖项 |
十、最佳实践
10.1 推荐方案
- 使用
computed:当需要计算衍生值时 - 使用
watch:当需要执行副作用时 - 使用
watchEffect:当需要自动追踪多个依赖项时
10.2 使用建议
- 在
computed中避免副作用操作 - 在
watch中处理异步操作时要添加错误处理 - 对复杂对象使用
deep选项 - 使用
immediate选项立即执行回调
10.3 避免使用的场景
- 当需要频繁触发更新时使用
watch而不是computed - 在
computed中进行复杂的计算逻辑 - 在
watch中进行大量的DOM操作
十一、总结
Vue3的watch和computed是处理响应式数据的核心工具,理解其工作原理和适用场景是构建高效应用的关键。通过合理使用这些特性,可以显著提升开发效率和应用性能。需要注意的是,要根据具体场景选择合适的工具,避免不必要的计算和副作用操作。在实际开发中,要特别注意性能优化和异常处理,确保应用的稳定性和可靠性。