在 Vue 3 中,computed 是一个核心 API,用于根据响应式数据(ref, reactive)派生出新的值。它具有缓存特性,只有当依赖的响应式数据发生变化时,才会重新计算。
1.写法
在 <script setup lang="ts"> 中使用时,主要分为 只读 (Read-only) 和 可写 (Writable) 两种模式。
- 只读计算属性 (最常用) 这是最常见的用法,传入一个 getter 函数,返回一个只读的 Ref 对象。
<script setup lang="ts">
import { ref, computed } from 'vue';
// 定义响应式数据
const count = ref<number>(1);
const price = ref<number>(100);
// 1. 自动推导类型 (Type Inference)
// TS 会自动推导出 doubleCount 的类型为 ComputedRef<number>
const doubleCount = computed(() => count.value * 2);
// 2. 显式指定类型 (Explicit Type)
// 使用泛型 <string> 强制指定返回类型
const priceMessage = computed<string>(() => {
return `当前价格: $${price.value}`;
});
console.log(doubleCount.value); // 输出: 2
// doubleCount.value = 3; // ❌ 报错:无法赋值,因为它是只读的
</script>
- 可写计算属性 (Getter + Setter)
如果你需要通过修改计算属性的值来反向更新源数据,可以传入一个包含
get和set的对象。
<script setup lang="ts">
import { ref, computed } from 'vue';
const firstName = ref<string>('John');
const lastName = ref<string>('Doe');
// 定义可写计算属性
const fullName = computed<string>({
// getter: 获取值
get() {
return `${firstName.value} ${lastName.value}`;
},
// setter: 设置值 (val 的类型会被自动推断为 string)
set(newValue) {
const names = newValue.split(' ');
firstName.value = names[0];
lastName.value = names[names.length - 1];
}
});
// 读取
console.log(fullName.value); // "John Doe"
// 写入 -> 触发 setter -> 更新 firstName 和 lastName
fullName.value = 'Alice Smith';
console.log(firstName.value); // "Alice"
</script>
2.使用场景
- 复杂的数据过滤与处理
避免在模板(Template)中编写复杂的逻辑。
<script setup lang="ts">
import { ref, computed } from 'vue';
interface User {
id: number;
name: string;
isActive: boolean;
}
const users = ref<User[]>([
{ id: 1, name: 'Allen', isActive: true },
{ id: 2, name: 'Bob', isActive: false },
{ id: 3, name: 'Chris', isActive: true },
]);
// 场景:只显示活跃用户
// 好处:如果 users 数组没有变化,多次访问 activeUsers 不会重新执行 filter,直接返回缓存
const activeUsers = computed(() => {
return users.value.filter(user => user.isActive);
});
</script>
- 动态样式类名绑定
根据多个状态生成最终的 CSS 类名对象或数组。
<script setup lang="ts">
import { ref, computed } from 'vue';
const isError = ref(false);
const isLoading = ref(true);
// 场景:根据状态返回类名对象
const buttonClasses = computed(() => ({
'btn-primary': true,
'btn-danger': isError.value,
'btn-loading': isLoading.value,
'disabled': isLoading.value // TS 能确保逻辑清晰
}));
</script>
<template>
<button :class="buttonClasses">Submit</button>
</template>
- 聚合数据
这是 computed 最经典的场景,依赖多个数据源进行计算。
<script setup lang="ts">
import { ref, computed } from 'vue';
const cartItems = ref([
{ name: 'Apple', price: 10, quantity: 2 },
{ name: 'Banana', price: 5, quantity: 5 },
]);
const discount = ref(0.9); // 9折
// 场景:自动计算总价
const totalPrice = computed<number>(() => {
const total = cartItems.value.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
return total * discount.value;
});
</script>