Code前端首页关于Code前端联系我们

Vue3里computed该怎么用?常见场景与冷门技巧全解析

terry 2小时前 阅读数 5 #SEO
文章标签 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传包含getset的对象:

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",输入全名时,firstNamelastName会自动拆分更新,适配表单联动场景。

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的依赖必须是refreactive包装的响应式数据,否则“依赖追踪”会失效。

哪些场景必须用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:多数据依赖聚合

像购物车总价(依赖多商品的pricequantity)、订单总金额(依赖商品价+运费+优惠)等多响应式数据的聚合计算,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",用户输入“张三 李四”时,firstNamelastName会自动变为“张三”和“李四”。

场景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非响应式)

解决:用refreactive包装普通变量,转为响应式数据:

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.bcomputed依赖a.b会更新;
若为ref({ a: { b: 1 } }),修改ref.value.a.bcomputed也会更新(因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,通过闭包访问refcount.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纯计算,勿混入副作用;
  • 依赖必须是refreactive包装的响应式数据;
  • 选项式与组合式API写法不同,但原理一致;
  • 复杂场景拆分计算逻辑,利用缓存提升性能。

无论是数据转换、表单联动还是性能优化,computed都是Vue响应式编程的“利器”,多实践多总结,自然能掌握其精髓~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门