在 Vue 开发里,computed 是一个看起来很简单、实际上非常值得吃透的能力。
很多人会把它理解成"一个会自动更新的变量",但真正写业务时,计算属性最容易踩坑的地方其实不是"怎么写",而是:
- 它什么时候执行
- 什么时候重新执行
- 为什么模板里用了它却没立刻算
- Vue2 和 Vue3 写法上到底差在哪
- 什么时候该用
computed,什么时候不该用
这篇文章我结合实战,把这些问题一次讲清楚。
一、computed 是什么
computed 是 Vue 提供的计算属性,它的核心作用是:
- 基于已有响应式数据,派生出一个新的值
- 自动缓存结果
- 只在依赖变化时重新计算
它适合做这类事情:
- 字符串拼接
- 数组筛选
- 状态派生
- 表单展示值加工
- 表格列名动态计算
- 权限/角色判断后的派生状态
比如:
js
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
模板里直接用:
vue
<div>{{ fullName }}</div>
看起来像一个字段,但它本质上是一个有缓存的函数结果。
二、computed 的核心特性
计算属性有三个非常重要的特性:
1. 有缓存
只要依赖没变,重复访问不会重复执行。
比如:
js
computed: {
total() {
console.log('computed run')
return this.a + this.b
}
}
如果页面中多处用到 total,在同一轮渲染里一般只会计算一次。
2. 依赖响应式数据
它依赖的必须是响应式数据,比如 data、props、ref、reactive、store 里的响应式状态等。
3. 适合派生,不适合副作用
computed 里应该是纯计算,不建议做这些事:
- 发请求
- 操作 DOM
- 赋值修改其他状态
- 写日志、副作用逻辑
三、computed 的触发时机
这是最容易搞混的部分,也是本文重点。
1. computed 不是一创建就立刻执行
很多人会误以为:
组件一初始化,computed 就全跑一遍
其实不是。
computed 是惰性求值的,也就是:
- 你不用它,它不算
- 你一旦访问它,它才算
2. 在模板中使用时,首次渲染会触发
比如:
vue
<template>
<div>{{ total }}</div>
</template>
这里的 total 会在首次渲染时被访问,因此会触发计算。
3. 依赖变化后,下一次访问才会重新计算
比如:
js
computed: {
total() {
console.log('run')
return this.a + this.b
}
}
如果 a 变了:
- Vue 会标记这个 computed "失效"
- 但不会立刻重新算
- 等下一次模板渲染或代码访问
total时,再重新计算
这就是"缓存 + 惰性"的组合。
4. 在 created、mounted、methods 中访问也会触发
只要你写了:
js
console.log(this.total)
就会触发计算。
所以如果你在 created() 里调用了某个方法,而方法里又访问了 computed,那它会在 created() 阶段被求值。
5. 多次访问通常不会重复执行
同一轮依赖没变的情况下:
js
console.log(this.total)
console.log(this.total)
通常只会真正计算一次,第二次直接取缓存结果。
四、实战里最常见的误区
误区 1:computed 能替代 methods
很多人会问:
既然 computed 也能写逻辑,那是不是 methods 都可以不用了?
不行。
区别很简单:
computed用于"依赖不变时结果稳定"的派生值methods用于"每次调用都可能不同"的函数逻辑
比如:
js
computed: {
fullName() {
return this.firstName + this.lastName
}
}
适合 computed。
但这个不适合:
js
methods: {
getNowTime() {
return new Date().toLocaleString()
}
}
因为它每次结果都可能不同,不该缓存。
误区 2:computed 里可以直接改数据
比如:
js
computed: {
status() {
this.loading = false
return this.list.length > 0
}
}
这是不推荐的。
computed 应该保持纯净,别在里面做赋值和副作用。
否则很容易出现:
- 逻辑混乱
- 重新渲染循环
- 调试困难
误区 3:computed 一定比 methods 快
不是绝对。
如果你只是简单调用一次,而且不需要缓存,那 methods 更直接。
computed 的价值在于:
- 多次使用同一个派生结果
- 依赖变化不频繁
- 逻辑适合缓存
五、computed 在实战中的典型用法
场景 1:动态筛选列表
js
computed: {
filteredList() {
return this.list.filter(item => item.name.includes(this.keyword))
}
}
模板中直接绑定:
vue
<el-table :data="filteredList" />
好处:
- 代码简洁
- 自动缓存
- 搜索框变化时自动更新
场景 2:动态标题/文案
js
computed: {
title() {
return this.role === 'admin' ? '管理员首页' : '普通用户首页'
}
}
适合做权限区分、项目区分、角色区分后的派生展示。
场景 3:表单联动值
js
computed: {
currentAreaCode() {
return this.useDeptId ? this.formData.deptId : this.formData.areaCode
}
}
这类写法特别常见,能避免在方法里写一堆 if/else。
场景 4:统计值和派生状态
js
computed: {
totalCount() {
return this.list.reduce((sum, item) => sum + item.count, 0)
}
}
如果模板里要多处展示总数,computed 会比方法更合适。
六、Vue2 中 computed 的写法
Vue2 里,computed 一般写在 computed 选项中。
基础写法
js
export default {
data() {
return {
firstName: 'Tom',
lastName: 'Lee'
}
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
}
}
带 get / set 的写法
Vue2 还支持完整对象写法:
js
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(value) {
const arr = value.split(' ')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
这种写法适合:
- 需要双向映射
- 需要对外暴露一个可写的派生值
- 例如表单字段联动、弹窗输入映射
七、Vue3 中 computed 的写法
Vue3 中,computed 仍然是核心 API,但写法更灵活,尤其在组合式 API 里更常见。
1. 组合式 API 写法
js
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('Tom')
const lastName = ref('Lee')
const fullName = computed(() => firstName.value + ' ' + lastName.value)
return {
firstName,
lastName,
fullName
}
}
}
这里要注意:
ref取值要用.valuecomputed返回的是一个只读的响应式引用
2. 带 set 的写法
js
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(value) {
const arr = value.split(' ')
firstName.value = arr[0]
lastName.value = arr[1]
}
})
Vue3 里这个写法也很常用。
3. <script setup> 写法
vue
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Tom')
const lastName = ref('Lee')
const fullName = computed(() => firstName.value + ' ' + lastName.value)
</script>
这是现在 Vue3 项目里最常见的写法之一。
八、Vue2 和 Vue3 中 computed 的对比
1. API 形式不同
Vue2:
js
computed: {
fullName() {
return this.firstName + this.lastName
}
}
Vue3:
js
const fullName = computed(() => firstName.value + lastName.value)
2. 响应式访问方式不同
Vue2 中直接 this.xxx。
Vue3 的 ref 需要 .value:
js
fullName.value
3. 逻辑组织方式不同
Vue2 更偏向"选项式写法":
- data
- methods
- computed
- watch
Vue3 更偏向"组合式组织":
- 相关状态和逻辑可以放在一起
- 更容易复用
- 更适合复杂业务拆分
4. 可读性侧重点不同
Vue2 的 computed 一般集中在一个 computed 区块里,适合中小型页面。
Vue3 的 computed 更适合和业务状态写在一起,比如:
js
const isAdmin = computed(...)
const tableData = ref([])
const canEdit = computed(...)
这样同一块业务逻辑更容易维护。
九、computed 的触发时机总结
可以简单记成一句话:
computed只有在被访问时才会执行,依赖变化后会失效,下一次访问时重新计算。
更细一点:
- 首次访问时执行
- 模板首次渲染时执行
- 方法/生命周期中访问时执行
- 依赖变化后下一次访问时重新执行
- 同一依赖状态下重复访问走缓存
十、什么时候用 computed,什么时候用 methods
用 computed 的情况
- 有明确依赖
- 结果可以缓存
- 一个值会被多次使用
- 逻辑是"派生计算"
例如:
- 是否显示某列
- 动态标题
- 过滤后的列表
- 合并后的展示字段
用 methods 的情况
- 每次调用都可能返回不同结果
- 有副作用
- 需要手动触发
- 不适合缓存
例如:
- 获取当前时间
- 发请求
- 提交表单
- 打开弹窗
- 执行一次性业务动作
十一、一个实战建议:把复杂 if/else 尽量前置成 computed
比如做角色区分、项目区分、区域级别控制时,最常见的问题就是模板里写满这种逻辑:
vue
v-if="($checkPermi(['xx']) && projectName(['xxx'])) || $checkPermi(['xxx'])"
这种写法短期能用,但长期会很难维护。
更好的方式是:
js
computed: {
canShowDistrict() {
return (this.($checkPermi(['xx']) && this.projectName(['xxx'])) || this.$checkPermi(['xxx']))
}
}
模板里就变成:
vue
v-if="canShowDistrict"
好处非常明显:
- 模板更干净
- 业务语义更清楚
- 后续改权限规则时更集中
- 不容易漏改某个地方
十二、最后总结
computed 不是"自动执行的变量",而是:
- 基于响应式数据的派生值
- 只有被访问时才计算
- 有缓存
- 依赖变化后才失效
- 适合做展示型、派生型逻辑
记住三句话就够了:
computed是惰性求值computed是有缓存的computed适合派生值,不适合副作用
Vue2 / Vue3 的差异也很简单:
- Vue2 更偏选项式写法
- Vue3 更偏组合式写法
- Vue3 里
ref要记得.value