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

Vue3里的computed和Composition API,这些问题你肯定想知道

terry 10小时前 阅读数 63 #SEO

刚接触Vue3 Composition API的同学,常常会疑惑:computed在组合式API里咋用?和之前Options API的版本有啥区别?碰到复杂场景咋处理?今天咱用问答的方式,把Vue3里computed和Composition API的事儿聊透,从基础用法到原理、实战场景全涵盖~

Vue3 的 computed 是用来解决什么问题的?

简单说,computed(计算属性)是帮我们基于已有响应式数据,生成“派生状态”的工具,举个例子:购物车页面里,商品的“数量”和“单价”是响应式数据,“总价”需要这两个数据计算得到——这时候用computed就特合适。

它核心解决两个痛点:

  • 缓存机制:只有依赖的响应式数据变化时,computed才会重新计算,比如多次访问“总价”,只要数量和单价没变,就直接用缓存结果,不用重复计算,性能比methods好(methods每次调用都执行)。
  • 响应式联动:依赖的数据变了,computed的结果自动更新,比如数量从2改成3,总价会自动从100变成150,不用手动触发更新。

再对比下methods和computed的区别:如果把“计算总价”写在methods里,每次组件渲染或事件触发时都会执行;但computed只在依赖变化时执行,其余时间复用缓存,所以涉及“依赖其他数据的派生逻辑”,优先用computed

在 Composition API 里怎么声明 computed?

在Composition API(也就是<script setup>setup()函数)中,用computed需要先从Vue里导入,然后分“只读”“可写”两种场景:

场景1:只读的 computed(只有 getter)

适合“根据已有数据生成新数据,不需要反向修改”的场景,用法是给computed传一个函数,返回计算后的值:

import { ref, computed } from 'vue'
// 定义基础响应式数据
const count = ref(3)  
// 定义计算属性:count的平方
const squared = computed(() => count.value * count.value)  
// 访问计算属性(必须用 .value)
console.log(squared.value) // 输出 9  
count.value = 4  
console.log(squared.value) // 依赖变化,重新计算,输出 16  

场景2:可写的 computed(同时有 getter 和 setter)

适合“需要双向联动”的场景,比如用户的“全名”由“名”和“姓”拼接而成,同时修改全名时要拆分赋值给名和姓,这时候给computed传一个对象,包含getset方法:

import { ref, computed } from 'vue'
const firstName = ref('John')  
const lastName = ref('Doe')  
// 可写的fullName:读的时候拼接,写的时候拆分  
const fullName = computed({  
  get() {  
    return `${firstName.value} ${lastName.value}`  
  },  
  set(newValue) {  
    // 假设传入 "Jane Smith",拆分给firstName和lastName  
    const [first, last] = newValue.split(' ')  
    firstName.value = first  
    lastName.value = last  
  }  
})  
// 读操作  
console.log(fullName.value) // 输出 "John Doe"  
// 写操作  
fullName.value = 'Jane Smith'  
console.log(firstName.value) // 输出 "Jane"  
console.log(lastName.value) // 输出 "Smith"  

Composition API 的 computed 和 Options API 版本有啥不一样?

用过Vue2的同学对Options API的computed很熟悉:在computed选项里写对象,每个属性对应一个计算函数,但Composition API的computed结构、复用性、作用域上都有变化:

结构更灵活,逻辑可“碎片化”组织

Options API的computed是“集中式”配置,所有计算属性必须塞到computed选项里;而Composition API的computed可以在setup随用随写,和相关的ref/reactive放在一起,逻辑更内聚。

比如一个“购物车模块”,在Options API里可能这样写:

export default {  
  data() { return { count: 1, price: 100 } },  
  computed: {  
    total() { return this.count * this.price }  
  }  
}  

在Composition API里,逻辑可以和数据紧耦合:

<script setup>  
import { ref, computed } from 'vue'  
const count = ref(1)  
const price = ref(100)  
const total = computed(() => count.value * price.value)  
</script>  

复用性更强,支持“组合式函数”

Composition API的computed能封装到自定义组合式函数里,方便跨组件复用,比如写一个useCounter函数,封装计数和“双倍值”的计算逻辑:

// useCounter.js  
import { ref, computed } from 'vue'  
export function useCounter(initialValue = 0) {  
  const count = ref(initialValue)  
  const doubled = computed(() => count.value * 2)  
  const increment = () => count.value++  
  return { count, doubled, increment }  
}  
// 组件中使用  
<script setup>  
import { useCounter } from './useCounter'  
const { count, doubled, increment } = useCounter(5)  
</script>  

Options API要复用计算属性,只能用mixins,但mixins容易引发命名冲突(多个mixins有相同名字的计算属性会覆盖),且逻辑分散,维护性差。

作用域更清晰,摆脱“this”绑定

Options API里的computed依赖this指向组件实例,容易因为this作用域问题踩坑;Composition API的computed直接和局部变量(ref/reactive)联动,没有this的心智负担,代码更简洁。

computed 背后的响应式原理是怎么运作的?

想理解computed的“自动更新”和“缓存”,得结合Vue3的响应式系统effect机制

  1. 依赖收集:当创建computed时,Vue会为它生成一个effect(副作用),这个effect会“跟踪”它依赖的响应式数据(比如refreactive里的属性)。
  2. 标记“脏值”:当依赖的响应式数据变化时,Vue会把这个computed标记为“脏”(需要重新计算)。
  3. 惰性求值:只有当你访问computed的.value时,才会检查是否是“脏”状态:
    • 如果是“脏”,执行计算函数,更新结果并标记为“干净”;
    • 如果是“干净”,直接返回缓存的结果。

举个直观的例子:

const num = ref(2)  
const squared = computed(() => num.value * num.value)  
// 第一次访问squared.value:  
// 触发计算,得到4,缓存结果,标记为“干净”。  
num.value = 3 // 依赖变化,squared被标记为“脏”。  
// 再次访问squared.value:  
// 发现是“脏”,重新计算(3×3=9),缓存结果,标记为“干净”。  

这种“依赖变化时标记脏值,访问时再计算”的设计,既保证了响应式自动更新,又通过缓存避免了不必要的性能消耗。

复杂业务逻辑里,computed 该怎么和其他逻辑配合?

实际开发中,computed很少单独用,常和ref/reactivewatch、甚至其他computed组合使用,分享两个典型场景:

场景1:表单验证的“组合验证”

比如用户注册表单,需要验证“用户名长度”“密码强度”“确认密码一致性”,最后还要一个“是否能提交”的总开关,这时可以用多个computed组合

import { ref, computed } from 'vue'  
// 基础数据  
const username = ref('')  
const password = ref('')  
const confirmPwd = ref('')  
// 单个字段的验证规则  
const isUsernameValid = computed(() => username.value.length >= 3)  
const isPasswordValid = computed(() => password.value.length >= 6)  
const isConfirmValid = computed(() => confirmPwd.value === password.value)  
// 组合所有规则,得到是否能提交  
const canSubmit = computed(() => {  
  return isUsernameValid.value && isPasswordValid.value && isConfirmValid.value  
})  

模板中直接用canSubmit控制按钮状态:

<button :disabled="!canSubmit">提交</button>  

场景2:和 watch 配合处理“计算后副作用”

如果计算结果变化后需要执行额外逻辑(比如埋点、请求),可以用watch监听computed:

import { ref, computed, watch } from 'vue'  
const searchQuery = ref('')  
const allItems = ref(['Vue', 'React', 'Angular'])  
// 搜索过滤后的结果(computed负责派生数据)  
const filteredItems = computed(() => {  
  return allItems.value.filter(item => item.includes(searchQuery.value))  
})  
// watch监听过滤结果,执行副作用(比如统计数量)  
watch(filteredItems, (newItems) => {  
  console.log(`当前匹配到${newItems.length}条结果`)  
  // 这里还能做埋点、触发请求等操作  
})  

这样分工明确:computed专注“数据派生”,watch专注“数据变化后的副作用”,代码可读性和维护性更高。

computed 执行出错了怎么处理?

计算过程中可能遇到错误(比如除以零、数据格式错误),这时候得“优雅兜底”,常见处理方式有两种:

方式1:computed 内部用 try...catch 捕获

在计算函数里用try...catch包裹逻辑,出错时返回默认值:

const divisor = ref(0)  
const result = computed(() => {  
  try {  
    return 10 / divisor.value // 可能触发除以零错误  
  } catch (err) {  
    console.error('计算出错:', err)  
    return 0 // 返回默认值,避免页面崩溃  
  }  
})  

方式2:处理异步场景的“空值兼容”

computed不支持直接写异步函数(因为需要同步返回值),所以如果依赖异步获取的数据,要先处理“数据未加载”的情况:

const asyncData = ref(null)  
const processedData = computed(() => {  
  if (asyncData.value) { // 数据加载完成后再处理  
    return asyncData.value.map(item => item.name)  
  }  
  return [] // 数据未加载时返回空数组,避免渲染错误  
})  
// 异步获取数据  
onMounted(async () => {  
  asyncData.value = await fetch('/api/data')  
})  

如果硬要在computed里处理异步,会导致返回Promise,破坏响应式逻辑——这时候应该用watchonMounted等钩子处理异步,再更新ref

实际开发中哪些场景必须得用 computed?

这3类场景,用computed既合理又高效:

场景1:依赖多个响应式数据的“联动计算”

比如电商场景的“购物车总价”:商品数量、单价、运费都是响应式数据,总价 = 数量×单价 + 运费,用computed可以自动跟踪这三个数据的变化,实时更新总价。

场景2:需要“缓存”的复杂计算

比如一个表格要根据“搜索关键词”“排序规则”过滤+排序数据,如果把过滤/排序逻辑写在methods里,每次组件渲染都会执行;但用computed的话,只有搜索词或排序规则变化时才重新计算,性能更好。

场景3:简化模板的“复杂表达式”

模板里如果写太多逻辑(比如多个变量拼接、条件判断),会让模板臃肿难维护,用computed把逻辑抽到JS里,模板只负责渲染:

<!-- 模板里少写逻辑,可读性更高 -->  
<p>{{ displayName }}</p>  
<script setup>  
const user = reactive({  
  isVip: true,  
  vipName: '钻石会员',  
  normalName: '小明'  
})  
const displayName = computed(() => {  
  return user.isVip ? user.vipName : `${user.normalName}(普通用户)`  
})  
</script>  

场景4:动态样式/类名的计算

根据多个状态动态生成class或style时,用computed能让逻辑更集中:

const isDisabled = ref(false)  
const isLoading = ref(false)  
const buttonClass = computed(() => ({  
  'btn-disabled': isDisabled.value,  
  'btn-loading': isLoading.value  
}))  

模板中直接绑定:

<button :class="buttonClass">提交</button>  

总结一下

Vue3 Composition API里的computed,本质还是“响应式派生+缓存”的工具,但用法更灵活、更贴近函数式编程,记住这几点:

  • 基础用法分“只读”和“可写”,按需选择;
  • 和Options API版本相比,组合式的computed更适合逻辑复用和碎片化组织;
  • 原理上靠“依赖收集+脏值标记+惰性求值”实现自动更新和缓存;
  • 复杂场景下,和ref/watch/其他computed配合,能高效处理表单、搜索、样式等需求;
  • 出错时用try...catch或空值兼容兜底,保证程序健壮性。

把这些搞明白,你在Vue3里用computed处理复杂逻辑就会顺手很多~如果还有疑问,评论区随时聊~

(全文完)

版权声明

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

热门