Vue3 Composition API的computed咋用?从基础到避坑一篇讲透
Vue3 Composition API的computed是干啥的?和选项式有啥不同?
computed 核心是 “响应式派生 + 缓存” ——基于已有响应式数据生成新的响应式数据,且重复访问时会缓存结果,避免无效计算,比如购物车总价由商品价格和数量推导而来,这些数据变化时,总价自动更新;多次用总价也不会重复计算。
和选项式API的computed对比,核心逻辑(依赖追踪、缓存)一致,但写法和灵活性有差异:
-
写法不同:
选项式是在组件computed选项里定义函数/对象;Composition API要导入computed函数,在setup或自定义函数里调用。选项式示例:
export default { data() { return { count: 1 } }, computed: { double() { return this.count * 2 } } }Composition API示例:
import { ref, computed } from 'vue' export default { setup() { const count = ref(1) const double = computed(() => count.value * 2) return { double } } } -
灵活性不同:
选项式的computed只能绑在组件选项里,逻辑拆分受限;Composition API的computed能在任意函数(比如自定义Hook)里用,方便把“数据+计算+方法”封装成独立逻辑(如useCart),复用性更强。
咋写基本的computed?只读和可写咋处理?
只读computed:派生单向数据
最常见场景:用已有响应式数据生成新值,步骤是导入computed→定义依赖的响应式数据→传入getter函数。
示例:点击按钮让计数翻倍显示
<template>
<p>计数:{{ count }}</p>
<p>双倍:{{ doubleCount }}</p>
<button @click="increment">+1</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(1)
// 只读computed:getter函数返回计算结果
const doubleCount = computed(() => {
console.log('计算doubleCount') // 验证缓存:仅依赖变化时执行
return count.value * 2
})
function increment() {
count.value++
}
</script>
点击按钮时,count变化触发doubleCount重新计算;模板多次用doubleCount时,只有count变化才会执行getter,其他时候读缓存。
可写computed:双向派生数据
场景:需要“派生新值”反向更新源数据”(比如表单联合字段),这时要给computed传包含get和set的对象。
示例:输入全名自动拆分姓和名,修改姓/名也更新全名
<template>
<input v-model="fullName" placeholder="输入全名(如:张三 三)" />
<p>姓:{{ firstName }}</p>
<p>名:{{ lastName }}</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 可写computed:get返回拼接值,set解析新值到源数据
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
// 拆分输入值(处理无空格等边界情况)
const [first, last] = newValue.split(' ')
firstName.value = first || ''
lastName.value = last || ''
}
})
</script>
输入框绑定fullName,用户输入后set触发,拆分值更新firstName和lastName;反之,修改firstName或lastName,fullName的get会重新计算。
computed的依赖追踪和缓存咋运作?为啥有时不更新?
依赖追踪:自动“盯紧”响应式数据
computed的getter执行时,Vue会自动收集用到的响应式数据(ref/reactive的属性) 作为依赖,依赖变化时,computed标记为“脏”,下次访问就重新计算。
比如doubleCount的getter用了count.value(count是ref),所以count是doubleCount的依赖。count变化→doubleCount重新计算。
缓存机制:减少无效计算
computed结果会缓存,只有依赖变化时才重新计算,多次访问computed变量,若依赖没变化,直接读缓存,这和method区别很大——method里的函数每次调用都执行,不管依赖变没变。
对比示例(看控制台打印):
<template>
<button @click="count++">+1</button>
<p>computed结果:{{ doubleComputed }}</p>
<p>method结果:{{ doubleMethod() }}</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(1)
const doubleComputed = computed(() => {
console.log('computed执行')
return count.value * 2
})
function doubleMethod() {
console.log('method执行')
return count.value * 2
}
</script>
点击按钮后,doubleComputed的getter仅执行1次(依赖变化时);doubleMethod每次渲染都执行(模板调用函数时)。
为啥不更新?常见“坑”
-
依赖非响应式:getter里用了普通变量(非ref/reactive),Vue没法追踪依赖。
错误示例:let rawCount = 1 // 普通变量,非响应式 const wrongDouble = computed(() => rawCount * 2) rawCount = 2 // wrongDouble不会更新!
解决:用
ref包装变量 →const count = ref(1)。 -
getter有异步/副作用:
computed设计为同步纯函数,不能放异步(如await)或副作用(如修改DOM),异步派生要用watch + ref。
错误示例:const asyncDouble = computed(async () => { await new Promise(resolve => setTimeout(resolve, 100)) return count.value * 2 // 异步导致依赖追踪失效 })解决:
const asyncDouble = ref(0) watch(count, async (newVal) => { await new Promise(resolve => setTimeout(resolve, 100)) asyncDouble.value = newVal * 2 }) -
循环依赖:两个
computed互相依赖,导致更新逻辑混乱,比如A依赖B,B依赖A,解决:检查业务逻辑,拆分或合并计算逻辑。
computed、method、watch咋选?各自适合啥场景?
三者职责明确,选对工具能少踩坑:
computed:派生响应式数据,要缓存
适合 “基于已有响应式数据,生成新响应式数据,且需缓存” 的场景:
- 购物车总价(由商品价格、数量推导)
- 表单联合字段(如全名由姓+名组成)
- 列表筛选结果(根据关键词过滤列表)
核心:自动追踪依赖、缓存结果,性能优,专注“数据派生”。
method:执行命令式逻辑,无缓存
适合 “不需要响应式追踪,只执行函数逻辑” 的场景:
- 事件处理(点击、提交)
- 纯工具函数(格式化时间、生成随机数)
- 复杂逻辑分步执行(如表单多步验证)
核心:每次调用都执行,无缓存,专注“执行动作”。
watch:响应变化做副作用,无缓存
适合 “响应数据变化,执行副作用(非纯函数操作)” 的场景:
- 数据变化后发请求(如搜索关键词变了,调接口拿列表)
- 数据变化后操作DOM(如滚动到指定位置)
- 监听多个数据源,合并处理
核心:主动监听变化,执行副作用,专注“响应变化做额外操作”。
综合示例:搜索功能
<template>
<input v-model="searchKey" placeholder="搜索关键词" />
<ul>
<li v-for="item in filteredList" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="clearSearch">清空搜索</button>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const rawList = ref([{ id: 1, name: 'Vue' }, { id: 2, name: 'React' }])
const searchKey = ref('')
// computed:派生过滤后的列表(数据派生+缓存)
const filteredList = computed(() => {
return rawList.value.filter(item => item.name.includes(searchKey.value))
})
// method:执行清空操作(命令式逻辑)
function clearSearch() {
searchKey.value = ''
// 还能加提示、埋点等操作
}
// watch:关键词变化后发请求(响应变化+副作用)
watch(searchKey, (newKey) => {
console.log('关键词变化,发请求:', newKey)
// fetch('/api/search?key=' + newKey).then(...)
})
</script>
开发中用computed容易踩哪些坑?咋避坑?
总结高频“踩坑”场景和解决方案:
坑1:依赖非响应式数据,computed不更新
现象:改了变量,computed结果没变化。
原因:变量不是ref/reactive,Vue追踪不到依赖。
解决:确保getter里的依赖都是响应式的(用ref包基本类型,reactive包对象/数组)。
坑2:在computed里写异步/副作用代码
现象:computed结果不对,或报错。
原因:computed是同步纯函数,不支持异步/副作用。
解决:异步逻辑用watch + ref,副作用用watch或method。
坑3:可写computed的setter逻辑不严谨,数据混乱
现象:改可写computed时,源数据没更新,或出现异常值。
原因:setter没处理边界情况(如用户输入格式不对)。
解决:在setter里做严格的输入校验和处理。
示例(完善全名拆分逻辑):
const fullName = computed({
get() { ... },
set(newValue) {
const parts = newValue.split(' ')
firstName.value = parts[0] || '' // 没姓就设空
lastName.value = parts[1] || '' // 没名就设空
}
})
坑4:过度使用computed,逻辑臃肿
现象:computed函数超长,维护困难,性能反而差。
原因:把复杂逻辑硬塞到一个computed里。
解决:拆分逻辑到多个computed或method,保持单一职责。
示例(拆分复杂计算):
// 原复杂computed
const total = computed(() => {
const subtotal = items.reduce(...)
const discount = subtotal > 100 ? 10 : 0
const tax = (subtotal - discount) * 0.08
return subtotal - discount + tax
})
// 拆分为多个computed
const subtotal = computed(() => items.reduce(...))
const discount = computed(() => subtotal.value > 100 ? 10 : 0)
const tax = computed(() => (subtotal.value - discount.value) * 0.08)
const total = computed(() => subtotal.value - discount.value + tax.value)
坑5:computed和watch的依赖混淆
现象:想监听computed变化,watch没触发。
原因:computed依赖没变化,或watch逻辑写反了(比如在watch里改computed导致循环更新)。
解决:明确watch的监听目标,避免循环依赖,若要基于computed做操作,确保其依赖真的变化,且逻辑合理。
咋利用computed封装可复用的逻辑?举个实际例子
Composition API的优势是逻辑复用,computed能把组件内的计算逻辑抽成独立Hook,多个组件复用。
示例:封装“购物车金额计算”逻辑(useCart.js)
// useCart.js
import { ref, computed } from 'vue'
export function useCart() {
// 购物车商品(响应式)
const items = ref([
{ id: 1, name: '商品A', price: 50, quantity: 2 },
{ id: 2, name: '商品B', price: 30, quantity: 3 }
])
// 计算属性:商品总数
const totalQuantity = computed(() => {
return items.value.reduce((sum, item) => sum + item.quantity, 0)
})
// 计算属性:商品总价(未折扣)
const subtotal = computed(() => {
return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
// 计算属性:折扣(满200减30)
const discount = computed(() => {
return subtotal.value >= 200 ? 30 : 0
})
// 计算属性:最终支付金额
const totalPrice = computed(() => {
return subtotal.value - discount.value
})
// 方法:添加商品
function addItem(newItem) {
items.value.push({ ...newItem, quantity: newItem.quantity || 1 })
}
// 方法:修改商品数量
function updateQuantity(itemId, newQuantity) {
const target = items.value.find(item => item.id === itemId)
if (target) {
target.quantity = newQuantity
}
}
return {
items,
totalQuantity,
subtotal,
discount,
totalPrice,
addItem,
updateQuantity
}
}
在组件中复用useCart
<template>
<div class="cart">
<h2>购物车</h2>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }} - ¥{{ item.price }} × {{ item.quantity }}
<button @click="updateQuantity(item.id, item.quantity - 1)">-</button>
<button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
</li>
</ul>
<p>商品总数:{{ totalQuantity }}</p>
<p>商品总价:{{ subtotal }} 元</p>
<p>折扣:{{ discount }} 元</p>
<p>最终支付:{{ totalPrice }} 元</p>
<button @click="addItem({ id: 3, name: '商品C', price: 20 })">添加商品C</button>
</div>
</template>
<script setup>
import { useCart } from './useCart.js'
const { items, totalQuantity, subtotal, discount, totalPrice, addItem, updateQuantity } = useCart()
</script>
这样,购物车的“数据+计算+方法”全被封装到useCart,其他组件要做购物车功能时,直接导入复用即可,代码复用性拉满~
computed的核心价值和实践建议
Vue3 Composition API的computed,核心是 “响应式派生 + 智能缓存” ,让我们高效处理“基于已有数据生成新数据”的场景,实际开发牢记这几点:
- 依赖必须是响应式的(
ref/reactive),否则更新不触发; - 区分
computed、method、watch的场景,别用错工具; - 复杂逻辑拆分到多个
computed或method,保持函数简洁; - 利用Composition API特性,把
computed和业务逻辑封装成可复用Hook,减少重复代码。
吃透这些,你就能在Vue3项目里用computed写出简洁、高效、易维护的响应式逻辑啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

