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

Vue3中computed属性怎么玩?从基础到避坑一次讲透

terry 6小时前 阅读数 43 #SEO
文章标签 Vue3 computed

啥是computed属性,为啥非得用它?

写Vue组件时,你有没有遇到过“模板里逻辑堆得像山,代码又乱又卡”的情况?比如要把 firstNamelastName 拼成全名,直接在模板里写 {{ firstName + ' ' + lastName }},要是多个地方需要全名,重复写就很冗余;要是计算逻辑更复杂(比如过滤、格式化),模板会变得像“迷宫”一样难维护。

这时候 computed属性 就成了“救星”——它是Vue封装的“响应式计算工具”,专门用来基于已有响应式数据生成新的衍生值,还是拼全名的例子,用computed定义一个fullName,模板里直接用{{ fullName }},代码瞬间清爽不少。

更关键的是,computed有个“隐藏Buff”:缓存机制,只要依赖的响应式数据没变化,多次访问computed值时,不会重复执行计算逻辑,性能比“每次重新算”好太多。

Vue3里咋声明和使用computed?

Vue3支持选项式API组合式API,两种写法各有特点,咱逐个拆解:

选项式API(经典写法)

在组件的computed选项里定义函数就行,比如拼全名:

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
}

模板里直接用<p>{{ fullName }}</p>,Vue会自动把fullName变成响应式的——只要firstNamelastName变化,fullName会跟着更新。

组合式API(setup语法糖)

得先从vue里导入computed函数,再用它创建计算属性,看例子:

<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// 只读的computed(只写getter)
const fullName = computed(() => firstName.value + ' ' + lastName.value)
// 可写的computed(带setter)
const fullNameWithSet = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    const [first, last] = newValue.split(' ')
    firstName.value = first
    lastName.value = last
  }
})
</script>

这里要注意:用ref定义的变量,访问时得加.value;如果是reactive对象里的属性,直接访问就行(因为reactive是代理对象)。

computed和methods有啥区别?该选哪个?

不少刚学的同学会纠结:“我用methods写个函数返回计算结果,不也一样?” 还真不一样,核心差异在执行时机和缓存

  • methods:每次调用函数都会重新执行逻辑,比如模板里写{{ getFullName() }},不管数据变没变,每次渲染都要跑一遍getFullName
  • computed:只有依赖的响应式数据变化时,才会重新计算;数据没变的话,直接返回缓存结果。

所以场景选择很明确:

  • 要是需要缓存计算结果(比如频繁使用的衍生值),选computed;
  • 要是逻辑是事件触发、主动调用(比如按钮点击执行的函数),选methods。

computed的缓存机制咋运作的?

举个例子直观感受:

<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => {
  console.log('计算double')
  return count.value * 2
})
</script>
<template>
  <button @click="count++">点我</button>
  <p>{{ double }}</p>
  <p>{{ double }}</p>
</template>

第一次渲染时,double会执行计算(打印“计算double”),然后缓存结果,模板里两次用double,第二次直接读缓存,不会重复打印,只有点击按钮让count变化时,double才会重新计算。

这机制的好处是减少不必要的计算,尤其是复杂逻辑(比如遍历大数组、模拟频繁IO操作的计算),能大幅提升性能。

computed能自动追踪响应式依赖?

没错!Vue的响应式系统会“悄悄”帮你监听computed函数里用到的响应式数据,比如这样:

<script setup>
import { ref, computed } from 'vue'
const num = ref(10)
const fixedNum = ref(5)
const sum = computed(() => num.value + fixedNum.value)
</script>

sum的依赖是numfixedNum,只要这俩有一个变化,sum就会重新计算。

但要是你用了非响应式数据(普通变量),情况就不一样了:

<script setup>
import { computed } from 'vue'
let num = 10 // 普通变量,不是ref/reactive
const sum = computed(() => num + 5)
num = 20 // 这里修改普通变量,sum不会更新!
</script>

因为Vue只能追踪refreactive包裹的响应式数据,普通变量的修改它“看不见”,所以computed不会触发更新,这也是新手常踩的坑,得记住:computed里要用响应式数据当依赖

复杂场景下,computed咋处理多依赖或异步?

多依赖:自动处理

只要computed函数里用到多个响应式数据,Vue会自动把它们都当成依赖,比如计算购物车总价,依赖商品列表和折扣:

<script setup>
import { reactive, computed } from 'vue'
const cart = reactive({
  products: [
    { name: '书', price: 50 },
    { name: '笔', price: 10 }
  ],
  discount: 0.8 // 8折
})
const total = computed(() => {
  let sum = cart.products.reduce((acc, p) => acc + p.price, 0)
  return sum * cart.discount
})
</script>

不管是products里的商品增减,还是discount变化,total都会自动更新——Vue帮你把所有用到的响应式数据都监听了。

异步逻辑:别直接用computed!

computed要求同步返回计算结果,如果逻辑里有async/await或者定时器,准出问题,比如想基于接口返回的数据计算:

<!-- 错误示范:computed里搞异步 -->
<script setup>
import { computed } from 'vue'
const asyncData = computed(async () => {
  const res = await fetch('/api/data')
  return res.json()
})
</script>

这时候asyncData拿到的是Promise,根本没法在模板里正常渲染。

正确做法是用watch配合computed:先用watch监听触发异步的条件,请求数据后更新响应式状态,再用computed基于这些状态计算。

<script setup>
import { ref, watch, computed } from 'vue'
const page = ref(1)
const data = ref([])
const processedData = computed(() => {
  // 基于data做过滤、排序等同步计算
  return data.value.filter(item => item.status === 'active')
})
watch(page, async (newPage) => {
  const res = await fetch(`/api/data?page=${newPage}`)
  data.value = await res.json()
})
</script>

computed和watch选哪个?

一句话说清核心差异:

  • computed:专注“计算衍生值”,是同步、有缓存的,输出一个新值;
  • watch:专注“监听数据变化后执行副作用”,可以异步、处理复杂逻辑,没有返回值(主要做操作,比如发请求、修改DOM)。

举个场景对比:

  • 要把用户输入的“年-月-日”字符串转成Date对象?用computed,因为是“衍生值计算”;
  • 要在用户修改生日后,自动把年龄同步到服务端?用watch,因为是“数据变化后执行异步操作”。

用computed容易踩的坑,咋避?

坑1:依赖非响应式数据

前面提过,要是computed里用了普通变量(没被ref/reactive包裹),数据变了computed也不更新。解决:确保依赖都是响应式的

坑2:过度嵌套,逻辑臃肿

比如一个computed里写了几十行代码,又要过滤又要排序还要格式化。解决:拆分成多个小computed,比如先写filteredList过滤数据,再写sortedList排序,最后用formattedList格式化——可读性和维护性都能提升。

坑3:可写computed的setter逻辑没处理好

前面讲了可写computed(带setter),如果set的时候没正确更新依赖,容易出问题。

<script setup>
import { ref, computed } from 'vue'
const first = ref('')
const last = ref('')
const full = computed({
  get() { return first.value + last.value },
  set(newVal) {
    // 错误:只改了first,last没动,但full的依赖是first和last
    first.value = newVal 
  }
})
</script>

这时候修改full,只有first变,last没变化,但full的getter依赖两个变量,逻辑就乱了。解决:setter里要把所有依赖的更新逻辑写全,比如拆分新值给first和last。

坑4:循环依赖

多个computed互相依赖(比如A依赖B,B依赖A),会导致更新时无限循环。解决:梳理逻辑,减少不必要的互相依赖,把公共逻辑抽成单独的computed或函数。

Vue3的computed属性是“响应式世界”里的高效工具,核心是缓存+自动追踪依赖,记住它的适用场景(计算衍生值、需要缓存),避开“依赖非响应式”“逻辑臃肿”这些坑,再结合methods、watch的差异选择,就能把组件逻辑写得又简洁又高效~

要是你刚开始用Vue3,建议多写小例子练手:试试只读computed、可写computed,故意踩踩“依赖普通变量不更新”的坑,感受下响应式系统的运作——实践出真知嘛!

版权声明

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

热门