computed 计算属性与 watch 监听器
Vue 3 的响应式系统不仅限于 reactive 和 ref,还提供了 computed 和 watch 两个强大工具,用于处理复杂的数据逻辑和副作用。computed 用于创建依赖响应式数据的计算属性,而 watch 用于监听数据变化并执行自定义操作。本节将详细讲解它们的用法、区别和最佳实践,帮助你更高效地管理响应式状态。
computed 计算属性
computed 创建一个基于其他响应式数据的衍生值,只有当依赖项变化时才会重新计算。
1. 基本用法
示例:计算全名
<template>
<div>
<input v-model="firstName" placeholder="名" />
<input v-model="lastName" placeholder="姓" />
<p>全名:{{ fullName }}</p>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('');
const lastName = ref('');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`.trim();
});
return { firstName, lastName, fullName };
}
};
</script>
- 效果:输入名和姓时,全名自动更新。
- 特点:
computed返回一个只读的Ref对象。- 在模板中自动解包为值,无需
.value。
2. 可写计算属性
Vue 3 支持通过 getter 和 setter 定义可写的计算属性:
<template>
<div>
<input v-model="fullName" placeholder="输入全名" />
<p>名:{{ firstName }}</p>
<p>姓:{{ lastName }}</p>
</div>
</template>
<script>
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('');
const lastName = ref('');
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`.trim(),
set: (newValue) => {
const [first, last] = newValue.split(' ');
firstName.value = first || '';
lastName.value = last || '';
}
});
return { firstName, lastName, fullName };
}
};
</script>
- 效果:输入全名时,自动拆分为名和姓。
- 用途:适合需要双向转换的场景。
3. 性能优势
- 缓存机制:
computed值会被缓存,只有依赖的响应式数据变化时才重新计算。 - 对比方法:普通方法每次渲染都会重新调用。
<template> <p>{{ computedValue }}</p> <p>{{ methodValue() }}</p> </template> <script> import { ref, computed } from 'vue'; export default { setup() { const count = ref(0); const computedValue = computed(() => count.value * 2); const methodValue = () => count.value * 2; return { count, computedValue, methodValue }; } }; </script>- 区别:
computedValue只在count变化时计算,methodValue每次渲染都执行。
- 区别:
watch 监听器
watch 用于监听响应式数据的变化,并在变化时执行副作用(如异步请求、日志记录)。
1. 基本用法
示例:监听计数器
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="count++">增加</button>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
setup() {
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`);
});
return { count };
}
};
</script>
- 效果:点击按钮时,控制台打印变化前后值。
- 参数:
- 第一个参数:监听的响应式数据。
- 第二个参数:回调函数,接收新值和旧值。
2. 监听对象属性
监听 reactive 对象的特定属性需要使用 getter 函数:
<template>
<div>
<p>{{ user.name }}</p>
<button @click="user.name = 'Bob'">修改姓名</button>
</div>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
setup() {
const user = reactive({ name: 'Alice', age: 25 });
watch(() => user.name, (newName, oldName) => {
console.log(`姓名从 ${oldName} 变为 ${newName}`);
});
return { user };
}
};
</script>
- 注意:直接
watch(user, ...)会监听整个对象的所有属性变化。
3. 选项配置
watch 支持选项:
immediate:立即执行一次。deep:深层监听对象。
<script>
import { ref, watch } from 'vue';
export default {
setup() {
const data = ref({ count: 0 });
watch(data, (newValue) => {
console.log('data 变化:', newValue);
}, { deep: true, immediate: true });
return { data };
}
};
</script>
- 效果:初始化时打印一次,
data.count变化时也触发。
4. 停止监听
watch 返回一个停止函数,用于手动取消监听:
<script>
import { ref, watch, onUnmounted } from 'vue';
export default {
setup() {
const count = ref(0);
const stop = watch(count, (newValue) => {
console.log('count:', newValue);
});
onUnmounted(() => stop()); // 组件销毁时停止监听
return { count };
}
};
</script>
computed vs watch
| 特性 | computed | watch |
|---|---|---|
| 用途 | 计算衍生数据 | 执行副作用 |
| 返回值 | 响应式值(Ref) | 无返回值(副作用函数) |
| 缓存 | 有缓存,依赖不变不重算 | 无缓存,每次变化都执行 |
| 使用场景 | 数据转换、格式化 | 异步操作、日志记录 |
示例:选择合适工具
<template>
<div>
<input v-model="price" type="number" />
<p>折扣价:{{ discountedPrice }}</p>
<p>日志:{{ log }}</p>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue';
export default {
setup() {
const price = ref(100);
const log = ref('');
// computed:计算折扣价
const discountedPrice = computed(() => price.value * 0.8);
// watch:记录价格变化
watch(price, (newPrice, oldPrice) => {
log.value = `价格从 ${oldPrice} 变为 ${newPrice}`;
});
return { price, discountedPrice, log };
}
};
</script>
- 效果:输入价格时,折扣价实时计算,日志记录变化。
最佳实践
- 优先使用 computed:
- 如果只是需要基于现有数据生成新值,选择
computed,性能更优。
- 如果只是需要基于现有数据生成新值,选择
- watch 用于副作用:
- 如 API 调用、动画触发等,使用
watch。
- 如 API 调用、动画触发等,使用
- 避免重复计算:
- 不建议在
watch中重复computed的逻辑。
- 不建议在
- 清理副作用:
- 在组件销毁时停止不必要的
watch,避免内存泄漏。
- 在组件销毁时停止不必要的
综合示例
实现一个简单的搜索框:
<template>
<div>
<input v-model="query" placeholder="搜索..." />
<p>结果数量:{{ resultCount }}</p>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue';
export default {
setup() {
const query = ref('');
const items = ref(['apple', 'banana', 'orange']);
// 计算属性:搜索结果数量
const resultCount = computed(() => {
return items.value.filter(item => item.includes(query.value)).length;
});
// 监听器:记录搜索日志
watch(query, (newQuery) => {
console.log(`搜索关键词:${newQuery}`);
}, { immediate: true });
return { query, resultCount };
}
};
</script>
- 效果:输入时,结果数量更新,控制台记录关键词。
总结
computed 和 watch 是 Vue 3 响应式系统的得力助手。computed 擅长处理依赖数据的计算,具有缓存优势;watch 适合响应变化执行副作用,灵活性更高。掌握这两者的使用场景和区别,能让你的代码更高效、可维护。下一节将探讨响应式开发的常见陷阱,帮你避开潜在问题!
