Vue基础教程(133)组件和组合API之响应式API中的computed()方法:别再用笨方法了!Vue3的computed()让你的代码自己会“偷懒”

嘿,各位前端摸鱼大师们,今天咱们来聊聊Vue3中那个能让代码自己“偷懒”的神奇功能——computed()。不知道你们有没有遇到过这种情况:页面上有个数据,需要根据其他好几个数据计算出来,然后你就在那儿手动监听这个、监听那个,写了一大堆代码,最后自己都绕晕了?

来,举个栗子🌰:你在做一个购物车,商品列表变了,总价要重新计算;商品数量变了,总价也要重新计算;甚至优惠券变了,总价还得重新计算... 这时候你是不是很想仰天长啸:“能不能让我消停会儿?!”

别急,computed()就是来拯救你的超级英雄!

一、computed()是什么鬼?

简单来说,computed()是Vue3组合式API中的一个响应式API,它能创建一个响应式的计算属性。这个计算属性的值会根据它依赖的其他响应式数据自动计算出来,而且只有在依赖发生变化时才会重新计算。

说白了,它就是你的私人小会计,你只需要告诉它:“小computed啊,帮我盯着这几个数,它们一变你就帮我算一下这个结果。”然后你就可以拍拍屁股走人,剩下的交给它了。

computed()的三大绝活:

  1. 自动追踪依赖:它像个侦探一样,能自动发现计算过程中用了哪些响应式数据,然后盯着它们。
  2. 缓存结果:这是最牛逼的地方!如果依赖的数据没变,它就直接返回上次计算的结果,不会重新计算。想想你的excel表格,某个单元格公式依赖的其他单元格没变,它就不会重新计算,一样的道理。
  3. 响应式更新:只要依赖的数据一变,它立刻重新计算,并且通知所有用到这个计算值的地方更新。
二、computed()怎么用?超简单!

使用computed()有两种方式,跟点菜一样,有简版和精装版。

方式1:只读模式(最常用)

这就是个老实本分的计算属性,只负责计算,不能直接修改。

import { ref, computed } from 'vue'

// 假设我们在setup函数里或者<script setup>中
const price = ref(10) // 单价
const count = ref(2) // 数量

// 创建一个计算属性:总价 = 单价 × 数量
const total = computed(() => {
  console.log('重新计算总价啦!') // 依赖变化时才会执行
  return price.value * count.value
})

// 试试看
console.log(total.value) // 输出:20,同时控制台会打印"重新计算总价啦!"

// 修改数量
count.value = 3
console.log(total.value) // 输出:30,控制台再次打印

// 再次访问,但依赖没变
console.log(total.value) // 输出:30,但控制台不会打印,因为用了缓存!

方式2:可读写模式(进阶玩法)

这种模式下,你既可以获取计算值,也可以设置它。设置时会触发一个回调函数,让你决定怎么处理。

import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// 可读写的计算属性
const fullName = computed({
  // 获取时计算全名
  get() {
    return `${firstName.value}${lastName.value}`
  },
  // 设置时拆分名字
  set(newValue) {
    console.log('有人想改全名:', newValue)
    // 假设新值是"李四"
    const names = newValue.split('')
    firstName.value = names[0] // "李"
    lastName.value = names[1] // "四"
  }
})

// 获取
console.log(fullName.value) // "张三"

// 设置
fullName.value = "李四"
console.log(firstName.value) // "李"
console.log(lastName.value) // "四"

是不是感觉已经开始香起来了?但这只是开胃小菜,下面咱们来点真家伙!

三、实战示例1:智能购物车

来,咱们用computed()实现一个会自己算账的智能购物车。

<template>
  <div class="cart">
    <h2>我的购物车 🛒</h2>
    
    <div v-for="item in items" :key="item.id" class="cart-item">
      <span>{{ item.name }} - ¥{{ item.price }}</span>
      <div>
        <button @click="decrease(item.id)">-</button>
        <span class="count">{{ item.count }}</span>
        <button @click="increase(item.id)">+</button>
        <button @click="remove(item.id)" class="remove">删除</button>
      </div>
    </div>
    <!-- 这里开始都是计算属性! -->
    <div class="summary">
      <p>商品总数:{{ totalCount }} 件</p>
      <p>商品总价:¥{{ totalPrice }}</p>
      <p v-if="hasDiscount">优惠折扣:-¥{{ discountAmount }}</p>
      <p class="final-price">实付金额:<strong>¥{{ finalPrice }}</strong></p>
      <p class="saving" v-if="hasDiscount">您节省了:¥{{ discountAmount }} 🎉</p>
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'

// 原始数据 - 购物车商品
const items = ref([
  { id: 1, name: 'Vue实战指南', price: 68, count: 1 },
  { id: 2, name: 'JavaScript高级程序设计', price: 99, count: 2 },
  { id: 3, name: 'CSS揭秘', price: 55, count: 1 }
])

// 优惠券相关
const hasCoupon = ref(true)
const discountRate = 0.8 // 8折

// 操作方法
const increase = (id) => {
  const item = items.value.find(item => item.id === id)
  if (item) item.count++
}

const decrease = (id) => {
  const item = items.value.find(item => item.id === id)
  if (item && item.count > 1) {
    item.count--
  }
}

const remove = (id) => {
  items.value = items.value.filter(item => item.id !== id)
}

// 🎯 重点来了!计算属性大军登场!

// 1. 计算商品总数量
const totalCount = computed(() => {
  return items.value.reduce((sum, item) => sum + item.count, 0)
})

// 2. 计算商品总价
const totalPrice = computed(() => {
  return items.value.reduce((sum, item) => sum + (item.price * item.count), 0)
})

// 3. 判断是否有折扣
const hasDiscount = computed(() => {
  return hasCoupon.value && totalPrice.value >= 100 // 满100才能用优惠券
})

// 4. 计算折扣金额
const discountAmount = computed(() => {
  if (!hasDiscount.value) return 0
  return totalPrice.value * (1 - discountRate)
})

// 5. 计算最终价格
const finalPrice = computed(() => {
  return totalPrice.value - discountAmount.value
})
</script>
<style scoped>
.cart {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}
.cart-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}
.count {
  margin: 0 10px;
  font-weight: bold;
}
.remove {
  margin-left: 10px;
  color: #ff4757;
}
.summary {
  margin-top: 20px;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 8px;
}
.final-price {
  font-size: 1.2em;
  color: #2ed573;
}
.saving {
  color: #ffa502;
}
button {
  padding: 5px 10px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
}
button:hover {
  background: #f1f2f6;
}
</style>

这个购物车的智能之处:

  • 你只管增删商品,其他的都交给computed属性
  • 总数量、总价格自动更新
  • 满足优惠条件时,折扣自动计算
  • 所有计算都是惰性的,只有相关数据变化时才重新计算
四、实战示例2:智能搜索过滤器

再来个更实用的:列表搜索过滤。用户在输入框打字,列表实时过滤,还显示统计信息。

<template>
  <div class="search-demo">
    <h2>员工查找器 🔍</h2>
    
    <div class="controls">
      <input 
        v-model="searchKeyword" 
        placeholder="输入姓名或部门搜索..."
        class="search-input"
      >
      <select v-model="selectedDepartment" class="department-select">
        <option value="">所有部门</option>
        <option value="技术部">技术部</option>
        <option value="市场部">市场部</option>
        <option value="人事部">人事部</option>
      </select>
    </div>
    <!-- 统计信息都是计算属性 -->
    <div class="stats">
      <p>
        显示 {{ filteredEmployees.length }} / 
        总共 {{ employees.length }} 名员工
        <span v-if="hasSearch">(搜索到 {{ searchResultCount }} 个结果)</span>
      </p>
    </div>
    <!-- 过滤后的员工列表 -->
    <div class="employee-list">
      <div 
        v-for="employee in filteredEmployees" 
        :key="employee.id"
        class="employee-card"
      >
        <h3>{{ employee.name }}</h3>
        <p>部门:{{ employee.department }}</p>
        <p>职位:{{ employee.position }}</p>
        <p>邮箱:{{ employee.email }}</p>
      </div>
    </div>
    <div v-if="noResults" class="no-results">
      😢 没有找到匹配的员工
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'

// 原始数据 - 员工列表
const employees = ref([
  { id: 1, name: '张三', department: '技术部', position: '前端工程师', email: 'zhangsan@company.com' },
  { id: 2, name: '李四', department: '技术部', position: '后端工程师', email: 'lisi@company.com' },
  { id: 3, name: '王五', department: '市场部', position: '市场经理', email: 'wangwu@company.com' },
  { id: 4, name: '赵六', department: '人事部', position: 'HR专员', email: 'zhaoliu@company.com' },
  { id: 5, name: '钱七', department: '技术部', position: '架构师', email: 'qianqi@company.com' }
])

// 搜索条件
const searchKeyword = ref('')
const selectedDepartment = ref('')

// 🎯 计算属性开始表演!

// 1. 过滤后的员工列表
const filteredEmployees = computed(() => {
  let result = employees.value
  
  // 按部门过滤
  if (selectedDepartment.value) {
    result = result.filter(emp => 
      emp.department === selectedDepartment.value
    )
  }
  
  // 按关键词搜索
  if (searchKeyword.value.trim()) {
    const keyword = searchKeyword.value.toLowerCase()
    result = result.filter(emp => 
      emp.name.toLowerCase().includes(keyword) ||
      emp.department.toLowerCase().includes(keyword) ||
      emp.position.toLowerCase().includes(keyword)
    )
  }
  
  return result
})

// 2. 搜索结果的统计
const searchResultCount = computed(() => {
  return filteredEmployees.value.length
})

// 3. 判断是否有搜索条件
const hasSearch = computed(() => {
  return searchKeyword.value.trim() || selectedDepartment.value
})

// 4. 判断是否没有结果
const noResults = computed(() => {
  return hasSearch.value && filteredEmployees.value.length === 0
})
</script>
<style scoped>
.search-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}
.controls {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}
.search-input, .department-select {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  flex: 1;
}
.stats {
  margin-bottom: 15px;
  color: #666;
}
.employee-list {
  display: grid;
  gap: 10px;
}
.employee-card {
  padding: 15px;
  border: 1px solid #e9ecef;
  border-radius: 8px;
  background: white;
}
.employee-card h3 {
  margin: 0 0 10px 0;
  color: #2c3e50;
}
.no-results {
  text-align: center;
  padding: 40px;
  color: #7f8c8d;
  font-size: 1.1em;
}
</style>

这个搜索过滤器的亮点:

  • 输入关键词或选择部门时,列表实时过滤
  • 统计信息自动更新
  • 没有结果时显示友好提示
  • 所有过滤逻辑都封装在computed属性中,模板超级干净
五、computed()的进阶技巧和避坑指南

技巧1:组合多个computed属性

computed属性可以依赖其他computed属性,形成计算链:

const basePrice = computed(() => price.value * count.value)
const tax = computed(() => basePrice.value * 0.1)
const finalPrice = computed(() => basePrice.value + tax.value)

技巧2:在watch中使用computed

import { watch } from 'vue'

// 监听计算属性的变化
watch(finalPrice, (newPrice, oldPrice) => {
  console.log(`价格从 ${oldPrice} 变为 ${newPrice}`)
})

避坑指南:

  1. 不要修改computed值(除非使用get/set模式)
  2. 避免在computed中有副作用(比如异步请求、修改DOM)
  3. 计算函数应该是纯函数,同样的输入永远得到同样的输出
六、computed() vs methods vs watch
  • computed:适合派生数据,有缓存,响应式依赖
  • methods:适合事件处理,无缓存,每次调用都执行
  • watch:适合监听变化执行异步操作或复杂逻辑

简单记:要计算用computed,要动作用methods,要监听用watch

总结

computed()就像是给你的Vue应用请了个不知疲倦的会计,你只需要定义好计算规则,它就自动帮你把所有的账算得明明白白。而且这个会计还特别聪明,同样的账不会算第二遍(缓存),只有数字变了才重新算。

学会了computed(),你会发现以前那些繁琐的手动更新、重复计算都是浮云。代码更简洁、性能更高、维护更容易,关键是——你可以更理直气壮地摸鱼了!

记住,好的程序员不是写代码最多的,而是写代码最聪明的。computed()就是让你变聪明的神器之一!

快去给你的Vue项目装上这个"自动计算大脑"吧,保证你用了就回不去了!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值