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

Vue3 computed 怎么帮我们做好响应式数据建模?

terry 2小时前 阅读数 11 #SEO
文章标签 Vue3 computed

Vue3 computed 到底是什么?

要理解 computed 在数据建模里的作用,得先从响应式和计算逻辑说起,Vue3 里的 computed 是个“聪明”的计算工具——它依赖响应式数据(ref reactive 包装的数据),能自动跟踪依赖变化,还会缓存计算结果。

举个简单例子:做个计数器,同时显示数值的双倍结果,代码长这样:

<template>
  <button @click="count++">{{ count }}</button>
  <p>双倍数值:{{ doubleCount }}</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>

这里 doubleCount computed,它的核心特点是 “依赖跟踪 + 缓存”:只要 count 没变,每次用 doubleCount 都是复用上次计算的结果,不用重复执行函数;只有 count 变化时,才会重新计算。

和 Vue2 相比,Vue3 的 computed 是通过 computed() 函数调用的,写法更灵活,能和 Composition API(setup 里的生命周期、自定义 Hook)深度结合,特别适合复杂组件的逻辑拆分。

用 computed 做数据建模,核心优势在哪?

“数据建模”本质是把业务里的状态、逻辑有条理地组织起来。computed 能解决两大痛点,让代码更易维护、更高效:

逻辑复用 + 可读性提升

复杂页面里,派生数据(比如购物车总价、用户权限判断)直接写在模板里,会让模板又乱又难维护,用 computed 把计算逻辑抽出来,模板只负责渲染,结构会清晰很多。

举个电商场景的例子:订单列表要显示“是否是高价值订单”,代码可以这样写:

<template>
  <div v-for="order in orders" :key="order.id">
    {{ order.name }} 
    <span v-if="isHighValue(order)">高价值</span>
  </div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const orders = reactive([
  { id: 1, name: '订单A', amount: 99 },
  { id: 2, name: '订单B', amount: 500 }
])
// 用 computed 封装“高价值判断”逻辑
const isHighValue = computed(() => (order) => order.amount > 300)
</script>

以后要改“高价值”的判断标准(比如从 300 改成 400),只需要改 computed 里的逻辑,不用在模板里到处找代码。

响应式自动更新

只要 computed 依赖的响应式数据变化,它会自动重新计算,比如做个 todo 列表,显示“未完成数量”:

<template>
  <p>未完成:{{ unDoneCount }}</p>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }} 
      <input type="checkbox" v-model="todo.done">
    </li>
  </ul>
</template>
<script setup>
import { reactive, computed } from 'vue'
const todos = reactive([
  { id: 1, text: '学习Vue', done: false },
  { id: 2, text: '写文章', done: false }
])
// 计算未完成数量
const unDoneCount = computed(() => {
  return todos.filter(todo => !todo.done).length
})
</script>

当某个 todo 的 done 状态变化时,todosreactive 包装的对象)里的数据变了,unDoneCount 会自动重新计算,模板也跟着更新——完全不用手动触发更新,这就是响应式的魅力。

深挖原理:computed 怎么跟踪依赖、实现缓存?

很多人用 computed 时只觉得“好用”,但想深入优化或排查问题,得理解背后的响应式机制,Vue3 的响应式基于 “effect(副作用)”“依赖收集(track)/触发更新(trigger)”

computed 的内部逻辑可以简单理解为:一个“懒执行”的 effect,普通 effectwatch 里的回调)是数据变了就立刻执行,但 computedeffect 默认是“懒”的——只有当它被使用时(比如模板渲染、被其他 computed 依赖),才会计算值,而且计算后会缓存结果。

用简化逻辑帮你理解(不是源码,只讲核心思路):

function computed(getter) {
  let value // 缓存计算结果
  let dirty = true // 标记是否需要重新计算
  const effectFn = effect(getter, {
    lazy: true, // 懒执行:不立刻运行 getter
    scheduler() {
      // 依赖变化时,标记为 dirty,下次使用时重新计算
      if (!dirty) {
        dirty = true
        trigger() // 触发更新(比如通知模板重新渲染)
      }
    }
  })
  return {
    get value() {
      if (dirty) {
        value = effectFn() // 执行 getter,计算新值
        dirty = false // 标记为“已缓存”
      }
      return value
    }
  }
}

关键点总结:

  • 第一次访问 computedvalue 时,才会执行 getter(因为 lazy: true),并缓存结果;
  • 当依赖的响应式数据变化时,scheduler 会把 dirty 设为 true,下次访问 value 时就会重新计算;
  • 没变化时,一直用缓存的 value,避免重复计算,提升性能。

用 computed 建模时,容易踩的坑有哪些?怎么避?

就算懂了基本用法,实际业务里稍不注意就会出问题,这几个高频坑要重点防范:

坑 1:在 computed 里修改响应式数据(导致循环依赖)

比如想“点击按钮,自动增加数值并同步更新双倍结果”,但错误地在 computed 里改数据:

// 错误示例!会报错或死循环
const count = ref(0)
const doubleCount = computed({
  get() { return count.value * 2 },
  set(newVal) { count.value = newVal / 2 }
})
function handleClick() {
  doubleCount.value = 10 // 试图通过 set 修改 count
}

computedsetter 里改数据,容易引发循环依赖(doubleCountget 依赖 countcount 的变化又触发 doubleCount 更新)。

解决办法computed 里尽量只做“计算”,把“修改逻辑”放到 methodwatch 里,如果必须用 setter,要确保依赖关系清晰,避免循环更新。

坑 2:依赖了非响应式数据,导致 computed 不更新

比如用普通变量当依赖,而不是 ref/reactive 包装的数据:

let normalNum = 0 // 普通变量,不是响应式的
const wrongComputed = computed(() => normalNum * 2)
function add() {
  normalNum++ // 改普通变量,Vue 感知不到变化
}

这时 wrongComputed 永远不会更新,因为 Vue 的响应式系统只跟踪 ref/reactive 包装后的数据。

解决办法:所有在 computed 里用的“数据源”,必须是响应式的,上面的例子改成:

const normalNum = ref(0) // 用 ref 包装,变成响应式
const rightComputed = computed(() => normalNum.value * 2)
function add() {
  normalNum.value++ // 改 .value,触发响应式更新
}

坑 3:computed 返回的是“只读”的 ref?

Vue3 中,computed 默认返回只读的 ref(除非你显式写了 setter)。

const count = ref(0)
const double = computed(() => count.value * 2)
double.value = 10 // 报错!默认的 computed 是 getter-only,没有 setter

如果需要让 computed 支持修改(比如双向绑定场景),必须显式写 setter

const count = ref(0)
const double = computed({
  get() { return count.value * 2 },
  set(val) { count.value = val / 2 }
})
double.value = 10 // 现在合法,count 会被改成 5

computed + 其他响应式API,建模场景怎么玩?

实际项目里,computed 很少单独用,往往和 refreactivewatchprovide/inject 配合,打造更复杂的状态管理逻辑,分享几个典型场景:

场景 1:和 reactive 结合,处理对象/数组的复杂计算

做一个“用户信息面板”,根据积分显示用户等级:

<template>
  <div>
    <p>昵称:{{ userInfo.name }}</p>
    <p>等级:{{ userLevel }}</p>
  </div>
</template>
<script setup>
import { reactive, computed } from 'vue'
const userInfo = reactive({
  name: '小明',
  score: 120 // 积分
})
// 根据积分计算等级
const userLevel = computed(() => {
  if (userInfo.score >= 200) return '黄金'
  if (userInfo.score >= 100) return '白银'
  return '青铜'
})
// 模拟积分变化
setTimeout(() => {
  userInfo.score = 150 // 修改 reactive 里的属性,触发 userLevel 更新
}, 3000)
</script>

场景 2:和 watch 配合,处理“计算后的数据变化”

购物车页面,当“选中商品总价”变化时,自动请求优惠信息:

<template>
  <div>
    <p>总价:{{ totalPrice }}</p>
    <p>优惠后:{{ discountPrice }}</p>
  </div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const goods = ref([
  { id: 1, price: 100, checked: true },
  { id: 2, price: 200, checked: false }
])
// 计算选中商品的总价
const totalPrice = computed(() => {
  return goods.value.filter(g => g.checked).reduce((sum, g) => sum + g.price, 0)
})
// 优惠后价格(模拟后端返回)
const discountPrice = ref(0)
// 当 totalPrice 变化时,请求优惠
watch(totalPrice, (newVal) => {
  // 模拟异步请求
  setTimeout(() => {
    discountPrice.value = newVal * 0.9 // 打 9 折
  }, 1000)
})
// 模拟勾选商品
setTimeout(() => {
  goods.value[1].checked = true // 选中第二个商品,totalPrice 变 300,触发 watch
}, 2000)
</script>

场景 3:和 provide/inject 结合,跨组件共享计算逻辑

App.vueprovide 用户权限信息,子组件用 computed 控制渲染:

<!-- App.vue -->
<script setup>
import { provide, reactive, computed } from 'vue'
const user = reactive({
  role: 'editor', // 角色:editor/admin 等
  name: '小红'
})
// 计算是否是管理员
const isAdmin = computed(() => user.role === 'admin')
provide('isAdmin', isAdmin)
provide('user', user)
</script>
<!-- Child.vue -->
<template>
  <div v-if="isAdmin">
    <button>删除数据</button> <!-- 管理员才有的操作 -->
  </div>
  <div v-else>
    <p>你好,{{ user.name }}</p>
  </div>
</template>
<script setup>
import { inject, computed } from 'vue'
const isAdmin = inject('isAdmin')
const user = inject('user')
// 子组件里基于注入的 isAdmin 做更细的计算
const showDeleteBtn = computed(() => isAdmin.value)
</script>

父组件用 computed 封装权限判断,子组件注入后直接用,保证了权限逻辑的单一来源,后续改权限规则(比如加超级管理员),只需要改 App.vue 里的 isAdmin 即可。

computed 在Vue3数据建模里的定位

简单说,computed“响应式数据的加工厂”:它吃进去的是响应式“原料”(ref/reactive 数据),加工出派生“成品”(计算后的数据),全程自动跟踪原料变化、自动更新成品。

在数据建模时,它能帮我们:

  • 把“计算逻辑”从模板和业务代码中解耦,让项目更易维护;
  • 利用缓存机制减少不必要的计算,提升性能;
  • 无缝衔接其他响应式 API,搭建“原始数据→计算数据→界面渲染/异步操作”的完整数据流。

别把 computed 只当“语法糖”,它是 Vue3 响应式系统里承上启下的关键工具,理解透它的用法、原理和边界(比如不能乱改数据、依赖必须响应式),才能在复杂项目里用它搭出清晰、高效的数据模型~

版权声明

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

热门