Vue3 计算属性

模板里写简单表达式很方便,但逻辑一旦复杂起来——比如多个价格相加再打折、拼接多个字段——塞进模板会让代码又长又难读。Vue3 的计算属性(computed)能把"由已有数据推导出新值"的逻辑集中起来管理,还自带缓存,是让模板保持干净的利器。

模板表达式之痛

先看一个把逻辑全写在模板里的反面例子:

<template>
  <p>{{ cart.reduce((s, i) => s + i.price, 0) > 100 ? '满100元包邮' : '未满100元' }}</p>
</template>

这样的表达式难读、难复用、难测试。计算属性的目标就是把推导逻辑抽出来,让模板只保留简单的变量名。

computed 基本用法

computed 接收一个计算函数,返回派生出来的新值,这个值可以像普通数据一样直接用在模板中:

<template>
  <p>总价:{{ total }} 元</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const price = ref(39)
const count = ref(2)
const total = computed(() => price.value * count.value)
</script>

效果:页面显示"总价:78 元"。之后 price 或 count 变化,total 会自动重新计算并刷新页面。

典型场景:列表派生统计

从一组数据里统计或过滤出展示用的结果,是 computed 最高频的场景:

<template>
  <p>已完成 {{ doneCount }} / {{ todos.length }} 项</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const todos = ref([
  { text: '写教程', done: true },
  { text: '敲代码', done: false },
])
const doneCount = computed(() => todos.value.filter((t) => t.done).length)
</script>

效果:页面显示"已完成 1 / 2 项"。勾选状态变化时,统计数字自动更新。

computed 与方法的区别

同样结果用方法也能实现,但 computed 会根据依赖缓存结果:依赖的数据没变化时多次访问直接返回缓存,方法则每次渲染都会重新执行:

<template>
  <p>{{ total }}</p>
  <p>{{ getTotal() }}</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const price = ref(39)
const total = computed(() => price.value * 2) // 依赖不变则不重算
const getTotal = () => price.value * 2        // 每次渲染都执行
</script>

结论:需要"跟随数据自动更新、又有一定计算开销"的值,优先用 computed。

可写的计算属性

computed 默认只读。若同时需要 getter 与 setter,可以传入包含两个函数的对象:

<script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
  get: () => firstName.value + lastName.value,
  set: (val) => {
    firstName.value = val[0]
    lastName.value = val.slice(1)
  },
})
fullName.value = '李四' // 触发 setter,姓名自动变为"李四"
</script>

日常开发几乎只用到 getter 形式,可写的计算属性请谨慎使用。

小结

computed 把复杂的派生逻辑从模板中抽离,职责单一、便于复用,并自带依赖缓存。它与下一节的 watch 同为响应式派生工具:computed 负责"算出新值",watch 负责"响应变化去执行逻辑"。

笔记加载中…