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

Vue3里computed咋用?这些场景和细节你搞懂没?

terry 16小时前 阅读数 116 #SEO
文章标签 Vue3 computed

不管是刚学Vue3的新手,还是想优化项目代码的老开发,“计算属性(computed)”都是绕不开的核心知识点,但你真的搞懂它啥时候用、咋用更高效了吗?今天咱从基础到实战,把computed的用法、区别、陷阱全拆明白~

computed基础用法是啥?选项式和组合式API写法有啥不同?

Vue里的computed,核心作用是基于已有响应式数据,生成新的响应式结果,简单说就是“依赖变了,我自动更新”,但选项式API和组合式API的写法差异还挺大,得分开看:

选项式API(Options API)写法

export default里配置computed选项,对象里每个属性是函数,返回计算后的值,比如做个“姓+名→全名”的功能:

<template>
  <div>全名:{{ fullName }}</div>
</template>
<script>
export default {
  data() {
    return {
      firstName: '张',
      lastName: '三'
    }
  },
  computed: {
    fullName() { 
      return this.firstName + this.lastName 
    }
  }
}
</script>

这里fullName依赖firstNamelastName,只要这俩值变了,模板里的fullName会自动更新。

组合式API(Composition API)写法

Vue3推荐的组合式API,需要先导入computed,再用函数式写法,还是上面的例子,代码变成这样:

<template>
  <div>全名:{{ fullName }}</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// computed接收一个函数,返回计算后的值
const fullName = computed(() => firstName.value + lastName.value)
</script>

注意哦!组合式API里用了ref,所以访问值得加.value;如果是reactive包裹的对象,直接点属性就行(比如user.firstName)。

computed和methods有啥区别?啥时候该用computed?

很多人初学容易搞混这俩,但核心差异是“缓存”,一句话总结:

  • methods里的函数,每次调用都重新执行
  • computed的计算结果,只有依赖的响应式数据变了才重新计算,否则复用缓存值

举个实际例子:做一个“数组过滤”功能,把数组里大于10的数筛出来。

用methods的写法

<template>
  <button @click="changeData">改数据</button>
  <div>筛选结果:{{ filterData() }}</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([1, 12, 8, 15])
function filterData() {
  console.log('methods执行了') // 每次渲染都会打印
  return list.value.filter(num => num > 10)
}
function changeData() {
  list.value.push(20)
}
</script>

点“改数据”时,list变化导致模板重新渲染,filterData()每次渲染都执行(哪怕list没变化,只要模板重新渲染,函数就会跑)。

用computed的写法

换成computed试试:

<template>
  <button @click="changeData">改数据</button>
  <div>筛选结果:{{ filterData }}</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const list = ref([1, 12, 8, 15])
const filterData = computed(() => {
  console.log('computed执行了') // 只有list变化时才打印
  return list.value.filter(num => num > 10)
})
function changeData() {
  list.value.push(20)
}
</script>

只有list真正变化时,filterData才会重新计算;如果其他不相关的响应式数据变化导致模板渲染,filterData会直接复用缓存结果,不会重复执行过滤逻辑。

啥时候选computed?

  • 当计算逻辑依赖响应式数据,而且会被多次调用/渲染时,用computed(比如模板里多次显示这个结果,或者在多个地方复用);
  • 如果是纯事件触发的逻辑(比如点击按钮执行一次),或者计算逻辑不依赖响应式数据,用methods更合适。

computed的缓存机制咋工作的?为啥有时依赖变了却没更新?

理解computed的“懒计算+缓存”逻辑,能避免很多 Bug。 Vue的响应式系统会自动跟踪computed的依赖项(也就是computed函数里用到的响应式数据),只有这些依赖项变化时,computed才会标记为“脏值”,下次访问时重新计算;如果依赖项没变化,直接返回缓存的结果。

缓存机制的好处

比如做一个“购物车商品总价”的计算,商品列表可能有几十上百条,每次渲染都重新遍历计算会很耗性能,用computed的话,只有商品的“价格”或“数量”变化时,才会重新计算总价,其他时候直接拿缓存,性能友好很多。

为啥依赖变了却不更新?常见坑点

最容易踩的坑是:computed函数里没正确访问响应式数据,导致Vue没法跟踪依赖,举个反面例子:

<script setup>
import { ref, computed } from 'vue'
// 错误示范:用了普通变量,不是响应式的
let normalVar = '我不是响应式的' 
const reactiveVar = ref('我是响应式的')
// computed里只用到普通变量,Vue无法跟踪依赖
const wrongComputed = computed(() => normalVar + reactiveVar.value)
function changeNormal() {
  normalVar = '变量变了' // 普通变量变化,computed不会更新
}
function changeReactive() {
  reactiveVar.value = '响应式变量变了' // 只有这个变化,computed才会更新
}
</script>

这里wrongComputed的依赖应该是normalVarreactiveVar,但normalVar不是响应式数据,Vue没法检测它的变化,所以即便normalVar改了,wrongComputed也不会更新。

怎么避免?

  • 所有在computed里用的可变数据,必须是refreactive包裹的响应式数据;
  • 如果依赖的是对象里的属性,确保用reactive包裹对象,或者用ref处理基本类型;
  • 检查computed函数里的逻辑,是不是真的“访问”到了依赖项(比如别把依赖写在条件判断里,导致没执行到)。

复杂业务逻辑里,computed咋处理多依赖或嵌套情况?

实际项目里,计算逻辑经常要结合多个响应式数据,甚至嵌套其他computed,这时候computed的“组合性”就很重要了——它能像积木一样,把多个小计算逻辑拼起来,让代码更分层。

多依赖场景:购物车总价计算

比如购物车有多个商品,每个商品有price(价格)和count(数量),要计算所有商品的总价:

<script setup>
import { reactive, computed } from 'vue'
// 模拟购物车数据:数组里每个对象是商品
const cart = reactive([
  { id: 1, name: '手机', price: 5000, count: 1 },
  { id: 2, name: '耳机', price: 1000, count: 2 }
])
// 计算单个商品的小计(price * count)
const itemTotals = computed(() => {
  return cart.map(good => good.price * good.count)
})
// 计算所有商品的总价(小计之和)
const totalPrice = computed(() => {
  return itemTotals.value.reduce((sum, cur) => sum + cur, 0)
})
</script>
<template>
  <div v-for="(good, idx) in cart" :key="good.id">
    {{ good.name }}:{{ good.price }} × {{ good.count }} = {{ itemTotals[idx] }}
  </div>
  <div>总价:{{ totalPrice }}</div>
</template>

这里totalPrice依赖itemTotals,而itemTotals依赖cart里的每个商品,当任意商品的pricecount变化时,itemTotals先更新,接着totalPrice也会自动更新——逻辑分层清晰,维护起来很方便。

嵌套computed的好处

  • 复用性:比如itemTotals可以在其他地方复用(比如计算优惠前的总价);
  • 可读性:把大计算拆成小步骤,像“先算每个商品小计→再算总和”,代码逻辑更直观;
  • 性能:每个computed只在自己的依赖变化时更新,不会重复计算无关逻辑。

听说computed能设置setter?这在啥场景下有用?

默认情况下,computed是只读的(只有getter),但Vue也支持给computed配置setter,实现“双向计算”,简单说:当你给computed赋值时,会触发setter里的逻辑,能反过来修改依赖的响应式数据。

场景:“全名”拆分到“姓”和“名”

比如用户编辑个人信息时,输入框绑定“全名”,但实际要把值拆分成firstNamelastName存起来,用setter就能实现:

<template>
  <input v-model="fullName" placeholder="输入全名(如:张 三)" />
  <div>姓:{{ firstName }}</div>
  <div>名:{{ lastName }}</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
const fullName = computed({
  // getter:拼接姓和名
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter:当给fullName赋值时,拆分到firstName和lastName
  set(newValue) {
    const [f, l] = newValue.split(' ') // 按空格拆分
    firstName.value = f || ''
    lastName.value = l || ''
  }
})
</script>

输入框输入“李 四”,firstName会变成“李”,lastName变成“四”;反过来,修改firstNamelastName,输入框里的fullName也会自动更新——这就是双向的计算逻辑!

啥时候用setter?

  • 表单场景:需要把一个输入值拆分成多个字段(出生日期”拆成“年/月/日”);
  • 配置项联动:整体开关”控制多个子开关,修改整体开关时,子开关状态同步变化(反过来,所有子开关关闭时,整体开关自动关闭);
  • 数据格式化:比如用户输入带单位的数值(如“100px”),setter负责拆分数值和单位,分别存到响应式数据里。

实际开发中,computed有哪些高频使用场景?

光懂语法还不够,得知道在真实项目里咋用,下面这几个场景,几乎每个Vue项目都会碰到,用computed能大幅简化代码~

场景1:表单实时验证

注册页里,密码需要满足“长度≥6、包含大写字母、包含数字”,用computed实时判断密码是否符合规则,比在methods里频繁调用函数更高效:

<template>
  <input v-model="password" type="password" placeholder="请输入密码" />
  <div v-if="!isPasswordValid" style="color: red;">
    密码需≥6位,且包含大写字母和数字
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'
const password = ref('')
const isPasswordValid = computed(() => {
  const reg = /^(?=.*[A-Z])(?=.*\d).{6,}$/
  return reg.test(password.value)
})
</script>

只要password变化,isPasswordValid会自动更新,模板里的错误提示也会实时切换。

场景2:列表筛选/搜索

商品列表页,根据用户输入的关键词过滤商品,用computed把“原列表+关键词”转成“过滤后列表”,模板直接渲染结果:

<template>
  <input v-model="searchKey" placeholder="搜索商品名称" />
  <ul>
    <li v-for="good in filteredGoods" :key="good.id">
      {{ good.name }} - ¥{{ good.price }}
    </li>
  </ul>
</template>
<script setup>
import { reactive, computed } from 'vue'
// 模拟商品数据
const goods = reactive([
  { id: 1, name: '无线鼠标', price: 99 },
  { id: 2, name: '机械键盘', price: 299 },
  { id: 3, name: '蓝牙音箱', price: 199 }
])
const searchKey = reactive('')
// 过滤逻辑写在computed里
const filteredGoods = computed(() => {
  return goods.filter(good => 
    good.name.includes(searchKey)
  )
})
</script>

用户输入时,searchKey变化→filteredGoods自动更新→列表自动重新渲染,逻辑和模板解耦得很干净。

场景3:数据格式化

后端返回的时间戳、图片路径等,需要前端格式化后展示,用computed把“原始数据”转成“展示用数据”,模板里不用写重复逻辑:

<template>
  <div>发布时间:{{ formatTime }}</div>
  <img :src="formatAvatar" alt="用户头像" />
</template>
<script setup>
import { ref, computed } from 'vue'
const rawTime = ref(1699999999000) // 后端给的时间戳
const rawAvatar = ref('user_avatar.png') // 后端给的头像路径
const baseImgUrl = 'https://xxx.cdn.com/' // 图片CDN基础地址
// 时间戳转YYYY-MM-DD
const formatTime = computed(() => {
  const date = new Date(rawTime.value)
  return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
})
// 拼接头像完整URL
const formatAvatar = computed(() => {
  return baseImgUrl + rawAvatar.value
})
</script>

以后如果要改时间格式或图片CDN地址,只需要改computed里的逻辑,不用动模板,维护性拉满~

场景4:购物车多维度计算

购物车页面需要计算“商品总价”“优惠后价格”“可选商品合计”等多个关联数据,用多个computed拆分逻辑,代码清晰又高效:

<template>
  <div>商品总价:{{ totalPrice }}</div>
  <div>优惠后价格:{{ discountPrice }}</div>
  <div>可选商品合计:{{ selectedTotal }}</div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const cart = reactive([
  { id: 1, name: '键盘', price: 299, count: 1, selected: true },
  { id: 2, name: '鼠标', price: 99, count: 2, selected: false },
  { id: 3, name: '耳机', price: 199, count: 1, selected: true }
])
const discountRate = 0.9 // 9折优惠
// 计算所有商品的总价(不管是否选中)
const totalPrice = computed(() => {
  return cart.reduce((sum, good) => sum + good.price * good.count, 0)
})
// 计算优惠后价格(总价×折扣)
const discountPrice = computed(() => {
  return totalPrice.value * discountRate
})
// 计算选中商品的合计
const selectedTotal = computed(() => {
  return cart.filter(good => good.selected)
    .reduce((sum, good) => sum + good.price * good.count, 0)
})
</script>

每个computed只负责一个维度的计算,依赖变化时精准更新,比把所有逻辑塞在一个函数里好维护太多~

computed的核心价值

从基础用法到复杂场景,computed的本质是“用声明式的方式,处理响应式数据的派生逻辑”,它帮我们解决了这些问题:

  • 减少重复计算,通过缓存提升性能;
  • 把复杂逻辑从模板和methods里抽离,让代码更易读、易维护;
  • 自动跟踪依赖,实现“数据变了,结果自动更新”的响应式体验。

下次写代码时,碰到“基于已有

版权声明

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

热门