Vue3里computed和ref有啥区别?该咋选?
日常写Vue3项目时,不少同学会纠结:“同样是响应式,ref和computed到底咋选?它们有啥本质区别?”今天咱们从功能、原理、场景这些角度拆解清楚,以后写代码不纠结~
先搞懂ref是干啥的?
ref是Vue3里最基础的响应式数据容器,不管你要存字符串、数字这类“基本类型”,还是对象、数组这些“引用类型”,用ref包一层,就能让数据变成“响应式”——意思是数据变了,页面能自动更新,其他依赖这个数据的逻辑也会跟着变。
举个栗子🌰:
做个登录页,用户名输入框的数据得是响应式的吧?用ref就很合适:
const username = ref('')
// 模板里用 v-model="username" ,输入时username.value会自动更新
再比如做个弹窗,控制显示隐藏的开关:
const isDialogShow = ref(false) // 点击“打开弹窗”按钮时,执行 isDialogShow.value = true
简单说,ref就像个“智能储物盒”:你往里面存数据(不管是简单值还是复杂对象),它帮你盯紧数据变化,一旦变了就通知Vue更新页面、触发依赖逻辑。
computed又是什么逻辑?
computed叫计算属性,它的核心是“基于已有响应式数据,推导新结果,还带缓存”。
啥意思?比如你有用户的“姓”和“名”两个响应式数据(用ref存的),现在要显示“全名”,如果每次用全名都手动拼接,既麻烦又容易漏更新,这时候用computed,它会自动监听依赖(姓和名),依赖变了才重新计算,没变就复用之前的结果(缓存)。
看例子🌰:
const firstName = ref('张')
const lastName = ref('三')
// 用computed推导全名
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
模板里用{{ fullName }},当firstName或lastName变化时,fullName会自动更新;如果两个都没变化,哪怕模板里多次用到fullName,computed也不会重复执行函数,直接拿缓存的结果,性能更优。
再举个复杂点的场景:购物车计算总价,商品列表是响应式数组(ref包的),每个商品有price(价格)和count(数量):
const cartItems = ref([
{ id: 1, price: 10, count: 2 },
{ id: 2, price: 20, count: 1 }
])
// 计算总价:每个商品的价格×数量相加
const totalPrice = computed(() => {
return cartItems.value.reduce((sum, item) => {
return sum + item.price * item.count
}, 0)
})
这时,只有当cartItems里的商品数量、价格变化时,totalPrice才会重新计算;如果商品列表没动,多次访问totalPrice都是直接拿缓存值,不用重复遍历数组求和,省性能。
核心区别到底在哪?
现在你大概有感觉了:ref是“存数据”的,computed是“加工数据”的,但具体差异得拆细了看——
功能定位:存储 vs 计算
- ref是“数据容器”:负责存原始数据(不管简单还是复杂),是响应式的“基石”,你要存个开关状态、用户输入、列表数据?选ref。
- computed是“数据加工厂”:本身不存原始数据,而是基于其他响应式数据(比如ref、reactive里的数据)推导新值,你要把多个数据拼起来、做复杂计算、格式化数据?选computed。
响应式触发逻辑:主动改 vs 自动跟
- ref的更新逻辑:你主动修改ref的.value,触发响应式更新,比如
count.value++,Vue会检测到count变化,然后更新所有用到count的地方。 - computed的更新逻辑:依赖的数据变了,computed自动更新,比如fullName依赖firstName和lastName,只有这俩变了,fullName才会重新计算;如果依赖没动,computed不会执行函数,直接返回缓存结果。
使用场景:啥时候用哪个?
举几个真实开发场景,你就明白边界在哪了:
| 场景描述 | 该用ref还是computed? | 为啥? |
|---|---|---|
| 存储用户输入的搜索关键词 | ref | 搜索关键词是“原始数据”,需要直接存储、修改 |
| 把姓和名拼成全名显示 | computed | 全名是“推导结果”,依赖姓和名,且需要缓存 |
| 购物车计算所有商品总价 | computed | 总价依赖商品列表的价格和数量,复杂计算需要缓存 |
| 控制弹窗显示隐藏的开关 | ref | 开关状态是“原始数据”,需要直接修改 |
| 把后台返回的时间戳格式化成“YYYY - MM - DD” | computed | 格式化后时间是“推导结果”,依赖原始时间戳 |
语法与可写性:能不能直接改?
- ref:可读可写,通过
.value修改数据,比如count.value = 10。 - computed:默认只读,你不能直接改
fullName.value(会报错);如果非要“可写”,得给computed传一个包含get和set的对象,在set里修改依赖的响应式数据。const fullName = computed({ get() { return `${first.value} ${last.value}` }, set(newVal) { // 假设newVal是“张 三”,拆分后修改first和last const [f, l] = newVal.split(' ') first.value = f last.value = l } }) // 这时可以执行 fullName.value = "李 四" ,会触发first和last的更新这种“可写computed”场景很少,一般用在表单双向绑定需要同时改多个字段的情况(比如全名输入框同时改姓和名)。
实战中容易踩的坑和误解
理解了区别,还要避开这些常见错误:
误解1:“computed能替代ref存数据”
不行!computed必须依赖其他响应式数据才能工作,比如你想存一个初始值为0的计数器,必须用ref:const count = ref(0);要是用computed,没依赖的话函数只执行一次就缓存,根本没法主动修改值。
误解2:“ref的计算不需要缓存,所以性能差”
不是的~ref是“存储数据”,每次访问的是当前值,修改时触发更新;computed是“计算数据”,有缓存是为了避免重复计算(比如复杂遍历、逻辑判断),两者设计目的不同,不存在谁性能更好,只看场景对不对。
坑1:在computed里写异步代码
computed的getter必须同步返回值,因为要立即拿到结果存缓存,如果要处理异步(比如调接口拿数据再计算),得用watch或者onMounted这类生命周期 + ref的组合,别硬往computed里塞异步。
坑2:直接修改computed的.value
默认情况下,computed返回的是只读ref,强行改fullName.value = 'xxx'会报错,如果业务需要“可写”,必须按上面说的配置set函数,且在set里修改依赖的响应式数据。
选ref还是computed?
记住一句话:“存数据用ref,算结果用computed”。
- 当你需要一个“响应式的容器”来存原始数据(不管是简单值还是对象),并且要能主动修改它 → 选ref。
- 当你需要基于已有响应式数据做“推导/计算”,并且希望结果能缓存(避免重复计算) → 选computed。
举个🌰加深记忆:做 TodoList 时,“待办事项列表”是原始数据 → 用ref存;“已完成事项的数量”是推导结果(依赖待办列表里每个项的完成状态) → 用computed算。
分清这层逻辑,写Vue3代码时就不会再纠结“该用ref还是computed”啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



