816 字
4 分钟
Vue 3 的 computed 计算属性

computed 做什么#

computed 从 getter 函数派生出一个新值,返回值是 Ref。getter 里读取的响应式数据就是它的依赖,依赖变化时重新计算,依赖不变时直接返回缓存结果,不会重复执行。

只有被读取时才会计算,这叫惰性求值。getter 必须是纯函数:只读取响应式数据,不产生副作用。

只读的 getter 写法#

只传一个 getter 函数时,返回值是只读 Ref,类型是 Readonly<Ref<Readonly<T>>>,对 .value 赋值会报错。

import { computed, ref } from 'vue'
const price = ref(0)
const label = computed<string>(() => '$' + price.value)
label.value // '$0'
label.value = '$1' // 只读,赋值会报错

模板里会自动解包,直接写 label 就行;在 JS 里要通过 .value 读取。

可写的 get/set 写法#

需要反向写入时,传一个带 get 和 set 的对象。get 仍然从依赖派生值,set 负责把写入的新值映射回依赖,这样得到的 computed 可读也可写。

import { computed, ref } from 'vue'
const price = ref(0)
const priceText = computed({
get: () => String(price.value),
set: (value: string) => {
// 输入框给的是字符串,存进 price 前先转成数字
price.value = Number(value) || 0
},
})

priceText 可以直接绑到 v-model 上:读取时拿到字符串,写入时转成数字存进 price。set 只负责把写入映射回依赖,可写 computed 只用在双向绑定这类场合。

购物车案例#

下面用两个 computed 处理同一份商品列表,一个按关键字过滤,一个汇总总价。

Cart.vue
<template>
<div>
<input v-model="keyWord" placeholder="请输入名称" type="text">
<table style="margin-top: 10px;" width="500" cellspacing="0" cellpadding="0" border>
<thead>
<tr>
<th>物品</th>
<th>单价</th>
<th>数量</th>
<th>总价</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in searchData">
<td align="center">{{ item.name }}</td>
<td align="center">{{ item.price }}</td>
<td align="center">
<button @click="item.num > 1 ? item.num-- : null">-</button>
<input v-model="item.num" type="number">
<button @click="item.num < 99 ? item.num++ : null">+</button>
</td>
<td align="center">{{ item.price * item.num }}</td>
<td align="center">
<button @click="del(index)">删除</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" align="right">
<span>总价:{{ total }}</span>
</td>
</tr>
</tfoot>
</table>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
interface Data {
name: string
price: number
num: number
}
const keyWord = ref('')
const data = reactive<Data[]>([
{ name: '小满的绿围巾', price: 100, num: 1 },
{ name: '小满的红衣服', price: 200, num: 1 },
{ name: '小满的黑袜子', price: 300, num: 1 },
])
const searchData = computed(() => {
return data.filter((item) => item.name.includes(keyWord.value))
})
const total = computed(() => {
return data.reduce((prev: number, next: Data) => {
return prev + next.num * next.price
}, 0)
})
const del = (index: number) => {
data.splice(index, 1)
}
</script>
  • searchData 依赖 data 和 keyWord,返回过滤后的列表
  • total 只依赖 data,把每行的单价乘数量累加

修改某行的数量时 total 重算;删除某一行时两个 computed 都失效;都等到下次读取时才计算,表格跟着更新。

什么时候用 computed#

computed 有缓存,依赖不变时不会重算;在模板里直接调用函数则每次渲染都会重新执行。由响应式数据算出来的派生、过滤、聚合结果适合放 computed。

异步请求、DOM 操作、修改其他状态都是副作用,放在事件处理函数或 watch 里,不要写进 getter。

第 7 篇,共 19 篇本文属于系列

Vue 3 的 computed 计算属性
https://www.choria.top/posts/vue3-computed/
作者
乔瑞雅·克林威尔
发布于
2026-04-08
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 214 天

部分内容可能已过时