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

Vue3里composable和computed怎么配合用?这些场景你肯定碰到过

terry 10小时前 阅读数 81 #SEO
文章标签 composable

做Vue3项目时,你有没有过这样的困惑:明明知道composable能复用逻辑,computed能做响应式计算,可放到一起咋用更顺手?遇到表单验证、状态联动这些场景,代码总写得啰嗦?今天咱就把composable和computed的配合用法掰碎了讲,从基础到场景一次搞明白。

先搞懂:Vue3的composable和computed分别是干啥的?

先拆概念。composable 可以理解成“能复用的逻辑块”,就像把组件里重复的逻辑(比如用户信息处理、表单验证)抽成单独的函数,哪个组件需要就import过来用,比如写个useUserInfo,里面处理用户头像、等级这些逻辑,多个页面复用起来超方便。

computed 是“响应式的计算属性”,它会自动跟踪依赖,只有依赖变了才重新计算,还能缓存结果,比如用户积分变了,等级自动更新,用computed写userLevel,依赖userScore,这样每次积分变,等级才重新算,性能好还省心。

把它们结合,就是把带响应式计算的逻辑,封装到可复用的composable里,比如用户等级计算这种逻辑,既需要响应式(积分变了等级自动变),又需要复用(多个页面显示用户等级),这时候composable+computed就派上用场了。

composable里用computed,能解决哪些实际开发痛点?

举个真实场景:做电商项目,多个页面要显示“用户会员等级”,等级由积分决定,积分变化时等级得实时更新,而且不同页面(首页、个人中心、订单页)都要显示。

要是不用composable+computed,每个组件都得写一遍“积分→等级”的逻辑,代码重复不说,后期改规则(比如积分阈值调整)得改N个地方,但用composable封装就不一样了:

// useUserLevel.js
import { ref, computed } from 'vue'
export function useUserLevel() {
  const userScore = ref(0) // 假设从接口拿到的积分,响应式数据
  // 用computed封装“积分→等级”的计算逻辑
  const userLevel = computed(() => {
    if (userScore.value >= 1000) return '黄金'
    if (userScore.value >= 500) return '白银'
    return '青铜'
  })
  // 暴露给组件用的方法和数据
  return { userScore, userLevel }
}

组件里用的时候,只需要:

<template>
  <div>当前等级:{{ userLevel }}</div>
  <button @click="userScore += 100">增加积分</button>
</template>
<script setup>
import { useUserLevel } from './useUserLevel'
const { userScore, userLevel } = useUserLevel()
</script>

这样好处很明显:

  • 逻辑复用:不管多少个组件要显示等级,导入useUserLevel就行,不用重复写计算逻辑。
  • 响应式自动管理:只要userScore变了,userLevel自动更新,不用手动监听。
  • 维护成本低:以后改等级规则,只需要动useUserLevel里的computed逻辑,所有用了这个composable的组件自动生效。

写composable时,computed的依赖管理要注意啥?

很多人第一次写会踩坑,明明数据变了,computed结果没更新”,核心是确保computed依赖的是响应式数据,以及别在computed里做副作用操作

依赖必须是响应式的

computed要跟踪变化,依赖的得是refreactive包装后的数据,比如下面错误写法:

// 错误示例:userScore不是响应式的
export function useUserLevel() {
  let userScore = 0 
  const userLevel = computed(() => { /* 依赖userScore */ }) 
  // 这里userScore是普通变量,改变时computed不会触发更新!
}

得改成ref包装:

const userScore = ref(0) // 响应式数据,变化会被computed跟踪

别在computed里搞“副作用”

computed是纯计算,只负责根据依赖返回结果,不能在里面做异步请求、修改DOM、修改其他响应式数据这些操作,比如下面错误写法:

const userLevel = computed(() => {
  // 错误:在computed里发请求,属于副作用
  axios.post('/log', { level: ... }) 
  return 计算后的等级
})

副作用要放到watch或者事件回调里,保持computed的“纯”,才能保证响应式逻辑稳定,避免意外的重复执行。

有没有典型场景,用composable+computed更丝滑?

除了用户等级,还有两个高频场景:表单验证状态联动

场景1:表单验证(比如登录页用户名验证)

很多页面都有表单,验证逻辑(长度、特殊字符、是否必填)重复度高,用composable封装验证逻辑,computed实时判断合法性:

// useFormValidate.js
import { ref, computed } from 'vue'
export function useFormValidate() {
  const username = ref('')
  // 用computed实时验证用户名
  const isUsernameValid = computed(() => {
    return username.value.length >= 3 && /^[a-z0-9]+$/i.test(username.value)
  })
  return { username, isUsernameValid }
}

组件里用:

<template>
  <input v-model="username" placeholder="请输入用户名" />
  <p v-if="!isUsernameValid">用户名需3位以上,只能包含字母数字</p>
  <button :disabled="!isUsernameValid">提交</button>
</template>
<script setup>
import { useFormValidate } from './useFormValidate'
const { username, isUsernameValid } = useFormValidate()
</script>

这样不管是登录页、注册页、修改资料页,导入useFormValidate就能复用验证逻辑,不用每个组件写一遍正则和判断。

场景2:状态联动(比如购物车商品总价计算)

购物车页面,商品数量变化时,总价要实时更新,把“商品列表→总价”的逻辑封装到composable:

// useCartTotal.js
import { reactive, computed } from 'vue'
export function useCartTotal() {
  const cartItems = reactive([
    { id: 1, price: 99, quantity: 1 },
    { id: 2, price: 199, quantity: 2 }
  ])
  // 计算总价:每个商品的price*quantity相加
  const totalPrice = computed(() => {
    return cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0)
  })
  // 暴露修改数量的方法
  function updateQuantity(productId, newQty) {
    const item = cartItems.find(item => item.id === productId)
    if (item) item.quantity = newQty
  }
  return { cartItems, totalPrice, updateQuantity }
}

组件里渲染:

<template>
  <div v-for="item in cartItems" :key="item.id">
    {{ item.price }}元 × {{ item.quantity }}
    <button @click="updateQuantity(item.id, item.quantity + 1)">+</button>
  </div>
  <div>总价:{{ totalPrice }}元</div>
</template>
<script setup>
import { useCartTotal } from './useCartTotal'
const { cartItems, totalPrice, updateQuantity } = useCartTotal()
</script>

这里totalPrice依赖cartItems的变化,不管是修改数量还是增减商品,总价自动更新,而且这个逻辑能复用到订单确认页、购物车弹窗等地方,不用重复写计算逻辑。

和传统Options API里的computed比,composable里的computed有啥不一样?

用过Vue2或者Vue3 Options API的同学,对computed不陌生,但放到composable里,有这几个关键区别:

作用域和复用性

Options API的computed写在computed选项里,属于当前组件实例,比如一个组件里写了computed: { userLevel() {} },只能在这个组件里用,其他组件想用得再写一遍。

而composable里的computed,是封装在可复用函数里,多个组件调用同一个composable时,每个组件拿到的computed是独立的实例,数据不会互相干扰,比如A组件调用useUserLevel,修改userScore,不会影响B组件里的useUserLevel实例。

逻辑组织方式

Options API是“按选项分类”(datacomputedmethods堆在一起),复杂组件里逻辑分散,找起来麻烦。

Composable是“按功能拆分”,把相关逻辑(比如用户等级计算、表单验证)打包成一个函数,代码结构更内聚,比如useUserLevel里,userScoreuserLevel、修改积分的方法(如果有的话)都在一个函数里,维护时不用在文件里跳来跳去。

响应式的“隔离性”

Options API的computed依赖的是组件的dataprops,而composable里的computed依赖的是自己函数内的响应式数据(比如refreactive在composable函数里定义),这意味着composable的逻辑是“自包含”的,组件只需要调用,不用关心内部怎么管理响应式,降低耦合度。

性能优化方面,composable+computed怎么避免重复计算?

computed本身有缓存机制:只有依赖变化时才会重新计算,但写composable时,还是要注意这两点,避免性能浪费:

减少不必要的依赖

比如在composable里,computed依赖的响应式数据要精准,举个反面例子:

const allData = reactive({ score: 0, name: '张三' })
const userLevel = computed(() => {
  // 这里依赖了allData的所有属性,但其实只需要score
  return allData.score >= 1000 ? '黄金' : '青铜'
})

如果allData.name变化,虽然不影响等级计算,但computed还是会重新执行(因为依赖了整个allData),改成用ref拆分:

const userScore = ref(0)
const userName = ref('张三')
const userLevel = computed(() => {
  return userScore.value >= 1000 ? '黄金' : '青铜'
})

这样只有userScore变化时,userLevel才会重新计算,减少不必要的执行。

合理使用shallowRef等优化手段

如果处理的是大对象,且只有部分属性变化,可以用shallowRef(浅响应式),比如用户信息是个大对象,但等级只依赖score字段:

const userInfo = shallowRef({ score: 0, name: '...', address: '...' })
const userLevel = computed(() => {
  return userInfo.value.score >= 1000 ? '黄金' : '青铜'
})

shallowRef只有顶层赋值会触发更新,内部属性变化不触发,这样如果只是修改userInfo.value.nameuserLevel不会重新计算,性能更好。

避免在computed里做 heavy 计算

如果计算逻辑很复杂(比如循环大量数据、复杂正则),即使有缓存,首次计算也会慢,这时候可以结合watch做懒加载,或者把计算拆分成多个小computed,比如计算“用户等级”和“等级对应的权益”分开:

const userLevel = computed(() => { /* 简单判断等级 */ })
const levelBenefits = computed(() => { /* 根据userLevel查权益,依赖userLevel */ })

这样userLevel变化时,levelBenefits才会重新计算,把大计算拆成小步骤,减少单次计算的压力。

composable+computed的核心优势

把这俩结合起来用,本质是“响应式计算逻辑的复用”,总结几个核心价值:

  • 提效:重复逻辑不用复制粘贴,一个composable到处复用。
  • 解耦:组件只关心“用什么逻辑”,不关心“逻辑怎么实现”,代码更干净。
  • 响应式自动管理:依赖变化时,computed自动更新,不用手动写watch
  • 易维护:逻辑集中在composable里,改需求只动一个地方。

实际项目里,只要碰到“多个组件需要一样的响应式计算逻辑”,比如权限判断、数据格式化、状态联动这些场景,都可以试试用composable封装computed的逻辑,刚开始可能觉得绕,但用几次就会发现,代码整洁度和开发效率提升不是一点半点~

(如果是新手,建议从简单场景练手,比如写个useTimeFormat的composable,用computed把时间戳转成YYYY - MM - DD格式,多个组件复用,感受一下流程~)

版权声明

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

热门