Vue3里computed该怎么用?常见场景与冷门技巧全解析
Vue3的computed是干啥的?和methods有啥不一样?
简单说,computed是基于依赖缓存的“计算属性”——它会根据依赖的响应式数据自动计算结果,且只有依赖变化时才重新计算。
和methods的核心区别很直观:methods里的函数每次调用都会执行(比如模板中用{{ formatTime() }},组件每次渲染都会重新执行formatTime);但computed的结果会被“缓存”,只有依赖的响应式数据变化时,才会重新计算。
举个实际例子:把时间戳转成“年-月-日”格式,用methods的话,哪怕时间戳没变化,组件每次重新渲染(比如其他数据更新),formatTime都会再执行;但用computed的话,只有时间戳本身变化时,才会重新计算格式化后的时间,性能更优。
选项式API(Options API)里咋用computed?
在选项式API中,需在computed选项里定义计算属性,写法为函数形式,返回计算后的值。
比如实现购物车总价计算:
export default {
data() {
return {
products: [
{ price: 10, quantity: 2 },
{ price: 20, quantity: 3 }
]
}
},
computed: {
totalPrice() {
// 依赖products中每个商品的price和quantity
return this.products.reduce((acc, cur) => {
return acc + cur.price * cur.quantity;
}, 0);
}
}
};
模板中直接用{{ totalPrice }}即可,此时只要products不变,不管组件如何重新渲染,totalPrice都会直接复用缓存结果,避免重复计算。
组合式API(Composition API)里computed咋用?
组合式API需先从Vue中导入computed函数,再用它创建计算属性,核心是处理ref/reactive包装的响应式数据,写法更灵活,还支持 setter(后文详解)。
场景1:只读的计算属性(仅getter)
比如实现数字翻倍:
import { ref, computed } from 'vue';
const count = ref(1);
// computed接收getter函数,返回计算后的值
const doubled = computed(() => count.value * 2);
// 模板中用{{ doubled }},count变化时,doubled自动更新
场景2:可写的计算属性(带setter)
有时需计算属性“可读可写”(如表单联合字段场景),此时给computed传包含get和set的对象:
import { ref, computed } from 'vue';
const firstName = ref('');
const lastName = ref('');
// 全名:firstName + lastName
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newValue) {
// 拆分输入的新值,更新firstName和lastName
const [f, l] = newValue.split(' ');
firstName.value = f || '';
lastName.value = l || '';
}
});
模板中用v-model="fullName",输入全名时,firstName和lastName会自动拆分更新,适配表单联动场景。
computed的依赖追踪和缓存咋工作的?
computed会自动“追踪”依赖的响应式数据(如ref/reactive包装的变量)。只有依赖的响应式数据变化时,computed才会重新计算;若依赖未变化,多次访问computed属性会直接取缓存结果。
举个例子:
const user = reactive({ name: '张三' });
const greeting = computed(() => `你好,${user.name}`);
此时greeting依赖user.name,若修改user.name = '李四',greeting会自动重新计算为“你好,李四”;但如果依赖非响应式数据(如普通变量let age = 18,且computed依赖age),age变化时computed不会更新——因为普通变量非响应式,Vue无法追踪其变化。
因此要牢记:computed的依赖必须是ref或reactive包装的响应式数据,否则“依赖追踪”会失效。
哪些场景必须用computed?
computed并非必须,但用对场景能减少重复代码、优化性能,这些场景用computed优势明显:
场景1:数据转换
如把后端返回的时间戳(如1677619200000)转为“2023-03-01”格式,用computed时,时间戳变化则格式化结果自动更新:
const timestamp = ref(1677619200000);
const formatTime = computed(() => {
return dayjs(timestamp.value).format('YYYY-MM-DD');
});
场景2:多数据依赖聚合
像购物车总价(依赖多商品的price和quantity)、订单总金额(依赖商品价+运费+优惠)等多响应式数据的聚合计算,computed能自动跟踪所有依赖:
const products = ref([...]); // 商品列表
const freight = ref(10); // 运费
const discount = ref(5); // 优惠
const total = computed(() => {
const productTotal = products.value.reduce(...);
return productTotal + freight.value - discount.value;
});
场景3:避免重复计算
若有复杂逻辑(如对数组做多层过滤+排序),每次渲染都执行会影响性能,用computed缓存结果,仅依赖(如过滤关键词、排序规则)变化时才重新计算:
const list = ref([...]); // 原始列表
const searchKey = ref(''); // 搜索关键词
const sortedList = computed(() => {
return list.value
.filter(item => item.name.includes(searchKey.value))
.sort((a, b) => a.price - b.price);
});
场景4:表单验证逻辑
如注册表单的“密码强度”,依赖password的实时输入,用computed实时计算强度等级:
const password = ref('');
const passwordLevel = computed(() => {
if (password.value.length < 6) return '弱';
if (password.value.match(/[A-Z]/) && password.value.match(/\d/)) return '强';
return '中';
});
场景5:响应式状态派生
如判断用户是否为VIP,依赖user.level:
const user = reactive({ level: 3 });
const isVIP = computed(() => user.level >= 5);
当user.level变化时,isVIP自动更新,模板中可直接用v-if="isVIP"控制显示。
computed和watch该咋选?
很多人混淆二者,核心区别是“计算值” vs “响应变化执行副作用”:
| 特性 | computed | watch |
|---|---|---|
| 核心作用 | 基于依赖生成新值(纯计算) | 响应值的变化,执行副作用(如请求) |
| 是否缓存 | 有缓存,依赖不变时复用结果 | 无缓存,每次变化都触发回调 |
| 执行时机 | 同步计算(依赖变化时立即更新) | 可异步(如配合async/await) |
| 适用场景 | 数据转换、多依赖聚合、表单验证等 | 发请求、修改DOM、复杂逻辑触发等 |
举个例子:
- 若需根据用户输入的关键词过滤列表,用
computed返回过滤后的列表(纯计算,依赖关键词); - 若需在用户输入完成后(如防抖500ms)发请求搜索,用
watch监听关键词,再执行异步请求(副作用)。
computed的setter有啥实用场景?
前文提过setter让计算属性“可写”,这些场景用setter很方便:
场景1:表单联合字段的双向绑定
如用户信息表单中,“全名”是firstName + lastName的组合,但界面只放一个输入框,用setter拆分输入值:
const firstName = ref('');
const lastName = ref('');
const fullName = computed({
get() { return `${firstName.value} ${lastName.value}`; },
set(val) {
const [f, l] = val.split(' ');
firstName.value = f || '';
lastName.value = l || '';
}
});
模板中用v-model="fullName",用户输入“张三 李四”时,firstName和lastName会自动变为“张三”和“李四”。
场景2:配置项的合并与拆分
如系统设置中,theme是对象{ color: 'red', size: 14 },但界面需一个输入框输入“red,14”,用setter拆分:
const theme = reactive({ color: 'red', size: 14 });
const themeStr = computed({
get() { return `${theme.color},${theme.size}`; },
set(val) {
const [c, s] = val.split(',');
theme.color = c || 'red';
theme.size = Number(s) || 14;
}
});
输入框绑定themeStr,修改时能自动更新theme对象的属性。
用computed容易踩的坑有哪些?咋解决?
computed好用但易踩坑,这些常见问题需规避:
坑1:依赖非响应式数据,导致不更新
若用普通变量作依赖:
let count = 1; // 普通变量,非响应式 const double = computed(() => count * 2); count = 2; // double不会更新,仍为2(因count非响应式)
解决:用ref或reactive包装普通变量,转为响应式数据:
const count = ref(1); const double = computed(() => count.value * 2); count.value = 2; // double会更新为4
坑2:循环依赖,导致更新异常
若两个computed互相依赖:
const a = computed(() => b.value + 1); const b = computed(() => a.value - 1);
这会引发死循环或更新异常。解决:梳理依赖关系,避免循环;逻辑复杂时改用watch处理。
坑3:过度依赖缓存,导致更新不及时
若依赖对象的深层属性,是否会触发更新?
Vue3的reactive是深层响应式的,修改reactive({ a: { b: 1 } }).a.b,computed依赖a.b会更新;
若为ref({ a: { b: 1 } }),修改ref.value.a.b,computed也会更新(因ref的.value是响应式的)。
但直接给对象重新赋值会失效:
const obj = reactive({ a: 1 });
obj = { a: 2 }; // 错误!reactive对象不能重新赋值,会失去响应性
解决:修改对象属性而非重新赋值,如obj.a = 2。
坑4:在computed里写副作用代码
computed应是纯函数(仅做计算,无副作用,如发请求、修改DOM、修改其他响应式数据),若写副作用,会导致逻辑混乱甚至死循环。
错误示例:
const count = ref(0);
const double = computed(() => {
count.value++; // 副作用:修改count,导致double反复更新
return count.value * 2;
});
解决:将副作用移到watch或生命周期钩子(如onMounted)中。
组合式API和选项式API的computed有啥区别?
两者核心功能一致(依赖追踪+缓存),但写法和场景有差异:
| 维度 | 选项式API(Options API) | 组合式API(Composition API) |
|---|---|---|
| 写法 | 在computed选项里定义函数 |
导入computed函数,在setup中使用 |
this指向 |
自动绑定组件实例(this.products) |
无this,通过闭包访问ref(count.value) |
| 逻辑复用 | 难(计算属性与组件强绑定) | 易(可抽成composable复用) |
举个逻辑复用的例子:将“数字翻倍”逻辑抽为通用函数:
// useDouble.js
import { ref, computed } from 'vue';
export function useDouble(initialValue) {
const count = ref(initialValue);
const doubled = computed(() => count.value * 2);
return { count, doubled };
}
// 组件中使用
import { useDouble } from './useDouble.js';
const { count, doubled } = useDouble(1);
组合式API的computed更适合逻辑复用,选项式API的computed更偏向组件内部计算。
咋优化computed的性能?
computed本身已做缓存,但复杂场景仍需优化:
优化1:减少不必要的依赖
若计算逻辑包含大量不相关响应式数据,尽量拆分,如:
// 不佳:依赖user.name和user.age,实际仅需name
const info = computed(() => `${user.name} - ${user.age}`);
// 更佳:拆分后,仅name变化时更新
const nameInfo = computed(() => `姓名:${user.name}`);
const ageInfo = computed(() => `年龄:${user.age}`);
优化2:拆分复杂计算
若computed包含大量逻辑(如多层循环、嵌套判断),拆为多个小computed,仅相关依赖变化时更新:
// 不佳:一个computed处理所有逻辑
const complex = computed(() => {
const filtered = list.value.filter(...);
const sorted = filtered.sort(...);
const mapped = sorted.map(...);
return mapped;
});
// 更佳:拆分后,各步骤独立缓存
const filteredList = computed(() => list.value.filter(...));
const sortedList = computed(() => filteredList.value.sort(...));
const mappedList = computed(() => sortedList.value.map(...));
优化3:避免在computed里做heavy操作
若计算逻辑含大量循环、DOM操作或Web Worker任务,勿放入computed——computed应仅做轻量计算。heavy操作放methods或单独工具函数,computed只负责调用结果:
// 不佳:computed中做heavy循环
const heavyResult = computed(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
});
// 更佳:heavy操作放methods,computed取结果
function calculateHeavy() {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
const heavyResult = computed(() => calculateHeavy());
优化4:利用缓存特性减少重复计算
若模板中频繁使用某计算后的值(如v-for中多次使用),用computed缓存比每次调用methods更高效。
computed的核心价值是“智能缓存 + 自动响应”
Vue3的computed并非语法糖,而是自动处理“依赖追踪”和“结果缓存”的工具,记住这些要点:
- 用
computed做纯计算,勿混入副作用; - 依赖必须是
ref或reactive包装的响应式数据; - 选项式与组合式API写法不同,但原理一致;
- 复杂场景拆分计算逻辑,利用缓存提升性能。
无论是数据转换、表单联动还是性能优化,computed都是Vue响应式编程的“利器”,多实践多总结,自然能掌握其精髓~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


