Code前端首页关于Code前端联系我们

Vue3 Composition API的computed咋用?从基础到避坑一篇讲透

terry 2小时前 阅读数 22 #SEO

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包含getset的对象

示例:输入全名自动拆分姓和名,修改姓/名也更新全名

<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触发,拆分值更新firstNamelastName;反之,修改firstNamelastNamefullNameget会重新计算。

computed的依赖追踪和缓存咋运作?为啥有时不更新?

依赖追踪:自动“盯紧”响应式数据

computed的getter执行时,Vue会自动收集用到的响应式数据(ref/reactive的属性) 作为依赖,依赖变化时,computed标记为“脏”,下次访问就重新计算。

比如doubleCount的getter用了count.valuecount是ref),所以countdoubleCount的依赖。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,副作用用watchmethod

坑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),否则更新不触发;
  • 区分computedmethodwatch的场景,别用错工具;
  • 复杂逻辑拆分到多个computedmethod,保持函数简洁;
  • 利用Composition API特性,把computed和业务逻辑封装成可复用Hook,减少重复代码。

吃透这些,你就能在Vue3项目里用computed写出简洁、高效、易维护的响应式逻辑啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门