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

Vue2里的computed该怎么用?这些知识点你得搞清楚

terry 12小时前 阅读数 11 #Vue
文章标签 Vue2;computed

不少刚接触Vue2的同学,一提到computed(计算属性)就犯嘀咕:它到底是干啥的?和methods有啥不一样?什么时候该用它?别慌,这篇文章就像给你搭个“知识脚手架”,把computed从基础到实战的门道掰开揉碎讲明白,看完你再用computed心里就有数啦~

computed到底是个啥?先把概念吃透

你可以把computed理解成「自动跟数据联动的计算器」,打个比方,做购物车时,每个商品有数量和单价,总价=数量×单价,要是用普通的data存总价,每次数量或单价变了,得手动更新总价,特麻烦,但用computed的话,定义一个totalPrice,里面写return 数量×单价,Vue会自动盯着数量和单价这两个“依赖”,只要它们变了,totalPrice就自动跟着变,根本不用你手动管~

再比如做搜索功能,输入框绑了searchKey,列表要显示匹配searchKey,这时候用computed写个filteredList,返回原列表过滤后的数据,只要searchKey或者原列表变了,filteredList自动更新,模板里直接用{{ filteredList }}就行,特别省心。

简单说,computed就是让你把「基于其他响应式数据计算出来的结果」,封装成一个像普通data一样能直接用的属性,而且更新还全自动~

computed和methods有啥区别?别再用混了

很多同学刚开始分不清computed和methods,其实核心区别就一个字:「缓存」。

先看methods:定义个methods里的函数formatTime(),每次在模板里调用{{ formatTime() }},或者在事件里触发,只要触发了就会执行函数里的代码,哪怕两次调用传的参数一样,它也会重新跑一遍逻辑,比如做时间格式化,每秒调用一次formatTime(),那它每秒都要执行计算,哪怕时间没变化(比如用户没操作),也会重复计算,浪费性能。

但computed不一样,它有「缓存机制」,比如定义computed里的formattedTime,依赖是data里的timeStamp,只有当timeStamp变了,formattedTime才会重新计算;如果timeStamp没动,不管在模板里用多少次{{ formattedTime }},它都直接拿之前算好的结果用,不会重复计算,这就像查字典,第一次查要翻书找,之后记住结果,下次直接说答案,效率高多了~

那啥时候用哪个?如果是「事件处理」(比如点击按钮执行逻辑)、「一次性计算」(比如页面加载时只算一次),用methods;如果是「基于响应式数据的动态计算,而且会被多次使用」,比如购物车总价、搜索过滤后的列表,用computed更省性能~

computed的缓存机制咋运作?理解了才好优化

Vue的computed能缓存,全靠「依赖追踪」这招,Vue会偷偷盯着computed里用了哪些响应式数据(比如data、props里的),这些数据就是computed的「依赖」,只要依赖没变化,computed就不会重新计算,直接返回上次的结果。

举个反面例子:假设在computed里写了个randomNum(),返回Math.random(),这时候Vue追踪不到依赖(因为Math.random()不是响应式数据),所以computed以为依赖没变化,就一直返回第一次的随机数,哪怕刷新页面(其实这时候值早变了),这就坑了,结果永远不对。

再比如用了全局变量window.total,然后computed里用这个total做计算,因为window.total不是Vue的响应式数据,Vue没法追踪它的变化,所以就算total变了,computed也不会更新,这时候要么把total放到data里(变成响应式),要么换methods/ watch来处理。

所以想让computed的缓存生效,必须保证它的依赖都是Vue能「看得到」的响应式数据,这样依赖一变,computed就自动更新,依赖不变就复用结果,性能拉满~

computed里能写异步逻辑吗?这些坑要避开

先说结论:computed默认不适合写异步,容易踩坑!因为computed的核心是「依赖追踪+缓存」,而异步操作(比如axios请求、setTimeout)的结果是没法被Vue及时追踪到的。

举个例子:想在computed里发请求,根据接口返回的用户等级,计算显示的头衔,代码大概长这样:

computed: {
  userTitle() {
    axios.get('/user').then(res => {
      return res.data.level > 5 ? '大佬' : '萌新'
    })
  }
}

这时候问题来了:axios是异步的,computed的userTitle一开始拿到的是Promise(不是最终的字符串),而且Vue没法追踪axios请求的结果啥时候回来,所以就算接口返回了新数据,userTitle也不会自动更新,模板里显示的还是错的。

那遇到要结合异步的场景咋办?分两种情况:

  1. 异步结果用来更新响应式数据:比如把接口返回的数据存到data里的userLevel,然后computed基于userLevel计算头衔,这样userLevel是响应式的,computed就能正常追踪啦~

  2. 纯异步逻辑+自动更新:这时候用watch更合适,比如监听某个触发条件(比如userId变化),触发watch里的异步请求,拿到结果后更新data,再让computed基于data计算。

computed主打「同步的、基于响应式依赖的计算」,异步这活儿,交给watch和methods搭配更稳~

computed的setter和getter咋玩?进阶用法得会

默认情况下,写computed都是只写getter,也就是返回计算后的值,但有时候需要「双向操作」,比如用户输入全名,自动拆分姓和名,这时候setter就派上用场了~

先看代码结构:

computed: {
  fullName: {
    // getter:读取fullName时执行
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter:给fullName赋值时执行
    set(newValue) {
      // 把newValue拆分成firstName和lastName
      const [first, last] = newValue.split(' ')
      this.firstName = first
      this.lastName = last
    }
  }
}

场景举例:表单里有个输入框绑了v-model="fullName",用户输入“张三 李四”(假设是全名格式),触发setter,把firstName设为“张三”,lastName设为“李四”;而当firstNamelastName变化时,getter触发,fullName自动变成“张三 李四”显示在输入框里,这样就实现了「一个计算属性双向联动多个数据」的效果~

这种用法常见在「复杂表单处理」「多字段联动」场景,比如用户填写地址时,省市区选择器联动,或者密码强度验证(输入密码后,computed根据密码复杂度返回强度等级,同时如果从其他地方设置强度等级,setter能反向更新密码规则),只要需要「计算属性被赋值时,反向更新依赖数据」,就可以用setter+getter的组合~

实战中computed咋解决复杂逻辑?这些技巧超实用

很多同学觉得computed只能处理简单计算,其实在复杂业务里,它能帮你把代码理得明明白白~分享几个实战场景:

场景1:数据过滤+排序一条龙

后台返回一个商品列表goodsList,前端要做搜索过滤(根据searchKey)和价格排序(根据sortType),要是把逻辑全堆在模板里,模板会变得巨复杂,而且不好维护,这时候用computed拆分:

computed: {
  filteredGoods() {
    // 第一步:过滤
    return this.goodsList.filter(good => 
      good.name.includes(this.searchKey)
    )
  },
  sortedGoods() {
    // 第二步:基于过滤后的结果排序
    return this.filteredGoods.sort((a, b) => {
      return this.sortType === 'asc' ? a.price - b.price : b.price - a.price
    })
  }
}

模板里直接用{{ sortedGoods }},只要goodsListsearchKeysortType有一个变化,filteredGoodssortedGoods会自动更新,逻辑拆分清晰,维护起来也方便~

场景2:多条件状态映射

订单列表里的status字段是数字(0-待支付,1-已支付,2-已完成…),要在模板显示对应的文字和样式,如果每个订单都在模板里写v-if/v-else,代码会很冗余,用computed封装:

computed: {
  orderStatus() {
    const statusMap = {
      0: { text: '待支付', class: 'pending' },
      1: { text: '已支付', class: 'paid' },
      2: { text: '已完成', class: 'done' }
    }
    return statusMap[this.order.status]
  }
}

模板里直接写{{ orderStatus.text }},class绑orderStatus.class,以后要加新状态,只需要在statusMap里加一行,不用动模板,扩展性拉满~

场景3:组合多个响应式数据

购物车页面,商品数量(num)和单价(price)都是响应式的,总价用computed:

computed: {
  totalPrice() {
    return this.num * this.price
  }
}

模板里{{ totalPrice }},不管num还是price变了,总价自动更新,而且如果多个地方要用总价(比如底部栏、商品卡片),用computed只算一次,比在每个地方写{{ num * price }}省性能又统一~

复杂逻辑一定要「拆分小computed」,比如计算用户等级时,要先算积分,再算等级,再算特权,可以拆成calcScore()calcLevel()calcPrivilege()三个computed,每个负责一步,代码可读性和可维护性直接起飞~

computed依赖管理容易踩哪些坑?避坑指南来了

用computed时,依赖管理没搞好,很容易出现「计算结果不对」「不更新」「性能差」这些问题,这几个高频坑一定要避开:

坑1:隐性依赖没处理

在computed里用了Vuex的state,但没注意state里的某个属性不是响应式的(比如直接给state加了个非响应式对象);或者用了this.$route.query里的参数,但没把参数放到data里转成响应式,这时候Vue追踪不到依赖变化,computed就僵住不更新了。

解决:确保所有依赖都是「响应式数据源」,比如Vuex的state(本身是响应式的)、组件的data、props,如果是路由参数这类,把它存到data里,再用computed依赖这个data属性。

坑2:循环依赖绕晕Vue

有两个computed:A依赖B,B又依赖A,这时候Vue不知道先更新谁,就会导致计算结果错乱,甚至死循环。

解决:梳理依赖关系,确保computed的依赖是「单向的、无环的」,比如把共同依赖抽到一个基础computed里,A和B都依赖这个基础computed,避免互相依赖。

坑3:过度封装,逻辑臃肿

为了“用computed而用computed”,把十几行复杂逻辑全塞到一个computed里,既难调试又难维护。

解决:拆分!把大逻辑拆成多个小computed,每个只做一件事,比如先做数据过滤,再做排序,再做格式化,每个步骤一个computed,可读性和可维护性直接翻倍。

坑4:依赖非响应式数据

在computed里用了Date.now()Math.random(),或者外部的window.globalVar,这些数据变化时,Vue没法检测到,导致computed缓存失效,结果永远是旧的。

解决:如果必须用这类数据,改用methods(每次调用重新计算),或者用watch监听这些数据的变化,再触发computed依赖的响应式数据更新。

说到底,computed是Vue2里帮我们「优雅处理动态计算逻辑」的大杀器,理解它的缓存机制、依赖追踪、和methods的区别,再结合setter/getter、拆分技巧,不管是简单的购物车总价,还是复杂的表单联动、数据处理,都能写得干净又高效,现在你可以打开项目,找个场景试试用computed重构代码,感受下它的丝滑~要是实践中遇到新问题,回头看看这篇里的知识点,大概率能找到答案~

版权声明

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

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门