Vue3 Computed 深入讲解(聚焦 Vue3 特性)

Vue3 的 Computed(计算属性)核心定位仍是「基于响应式依赖的缓存式派生值计算」,但在组合式 API 加持下,用法更灵活、与响应式系统(ref/reactive)的配合更紧密,同时保留了 Vue2 中经实践验证的核心优势(依赖追踪+缓存)。以下从「核心原理→基础用法→高级场景→Pinia 集成→避坑指南」全面拆解 Vue3 专属特性。

一、Vue3 Computed 核心原理(与 Vue2 差异)

Vue3 基于 Proxy 实现响应式系统,Computed 的底层逻辑虽延续「依赖收集→缓存→触发更新」,但有两处关键优化:

  1. 依赖追踪更精准Proxy 能监听对象/数组的所有操作(如 push/delete/索引修改),无需像 Vue2 那样对数组方法重写,Computed 对数组依赖的追踪更无死角;
  2. 惰性求值增强:仅当 Computed 的「最终结果被访问」时才会触发首次计算(Vue2 部分场景存在提前计算的情况),未被使用的 Computed 即使依赖变化也不会执行,性能更优;
  3. 缓存机制不变 :仅依赖的响应式数据(ref/reactive/Pinia 状态等)变化时,Computed 才会标记为「脏状态」,下次访问时重新计算并更新缓存。

二、基础用法:组合式 API 核心写法

Vue3 推荐使用 setup 语法糖(<script setup>),Computed 需通过 import { computed } from 'vue' 显式引入,支持「只读 Computed」和「可写 Computed」两种核心形式,且返回值均为 ref 对象(模板中自动解包,脚本中需用 .value 访问)。

1. 只读 Computed(最常用)

接收一个「计算函数」,返回只读的 ref 对象,适用于「基于现有响应式数据派生值」(如过滤、统计、格式转换)。

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

// 1. 基础用法:依赖 ref 数据
const count = ref(1)
const doubleCount = computed(() => {
  console.log('仅依赖变化时执行') // 首次访问/count变化时触发,否则复用缓存
  return count.value * 2
})

// 2. 依赖 reactive 数据
const user = reactive({
  firstName: '李',
  lastName: '四'
})
const fullName = computed(() => `${user.firstName}${user.lastName}`)

// 3. 嵌套 Computed:依赖其他 Computed
const formattedName = computed(() => `用户姓名:${fullName.value}`)

// 脚本中访问需加 .value(模板中直接用 {{ doubleCount }} 即可)
console.log(doubleCount.value) // 2
count.value = 2
console.log(doubleCount.value) // 4(触发重新计算)
</script>

<template>
  <div>计数:{{ count }}</div>
  <div>计数翻倍:{{ doubleCount }}</div> <!-- 自动解包,无需 .value -->
  <div>{{ formattedName }}</div> <!-- 输出:用户姓名:李四 -->
</template>
  • 关键:计算函数内必须访问响应式数据的「响应式属性」 (如 count.valueuser.firstName),否则无法被依赖追踪。

2. 可写 Computed(修改原始数据)

接收一个包含 get(读取逻辑)和 set(修改逻辑)的对象,适用于「通过计算属性反向修改原始响应式数据」(如表单联合输入、数据拆分)。

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const width = ref(100)
const height = ref(200)

// 可写 Computed:面积(修改面积时反向计算宽高)
const area = computed({
  // get:读取时执行(同只读 Computed 逻辑)
  get() {
    return width.value * height.value
  },
  // set:修改 area 时执行,接收新值
  set(newArea) {
    // 假设宽高比保持 1:2,反向修改原始数据
    width.value = Math.sqrt(newArea / 2)
    height.value = width.value * 2
  }
})

// 修改 Computed(触发 set 方法)
area.value = 800
console.log(width.value) // 20,height.value:40(符合 1:2 比例)
</script>
  • 场景:如「尺寸调整器」(面积→宽高)、「日期范围选择」(总天数→开始/结束日期)、「密码强度计算」(输入值→强度等级,反向修改提示文案)。

三、高级场景:Vue3 专属用法

1. 与 reactive 嵌套对象的配合

Vue3 的 Proxy 支持深层响应式,Computed 可直接依赖 reactive 嵌套对象的属性,无需额外处理:

vue 复制代码
<script setup>
import { reactive, computed } from 'vue'

const goods = reactive([
  { id: 1, name: '笔记本', price: 5999, stock: 10 },
  { id: 2, name: '鼠标', price: 299, stock: 30 }
])

// 计算有库存的商品数量
const availableGoodsCount = computed(() => {
  return goods.filter(item => item.stock > 0).length
})

// 计算商品总价(依赖嵌套属性 price/stock)
const totalStockValue = computed(() => {
  return goods.reduce((sum, item) => sum + item.price * item.stock, 0)
})

// 修改嵌套属性,触发 Computed 更新
goods[0].stock = 5
console.log(availableGoodsCount.value) // 2(仍有库存)
console.log(totalStockValue.value) // 5999*5 + 299*30 = 40960
</script>

2. 结合防抖/节流(处理高频计算)

Computed 本身无防抖节流能力,但可与 lodashdebounce/throttle 结合,处理「输入搜索过滤」等高频触发场景(需注意:防抖逻辑需包裹计算函数,且需手动处理 ref 依赖):

vue 复制代码
<script setup>
import { ref, computed } from 'vue'
import { debounce } from 'lodash-es'

const searchInput = ref('')
const goods = ref([/* 商品列表 */])

// 防抖处理:输入停止 300ms 后再执行过滤(避免输入时频繁计算)
const debouncedFilter = debounce((val) => {
  return goods.value.filter(item => item.name.includes(val))
}, 300)

// Computed 依赖 searchInput,触发防抖过滤
const filteredGoods = computed(() => {
  return debouncedFilter(searchInput.value)
})
</script>

<template>
  <input v-model="searchInput" placeholder="搜索商品" />
  <div v-for="item in filteredGoods" :key="item.id">{{ item.name }}</div>
</template>
  • 注意:防抖函数需用 lodash-es(ES 模块版本),避免 CommonJS 模块导致的打包问题。

3. 依赖异步数据(配合 async/await

Computed 本身不支持 async/await(计算函数需同步返回值),若需依赖异步数据(如接口请求结果),需先通过 ref 存储异步结果,再用 Computed 基于该 ref 计算:

vue 复制代码
<script setup>
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'

// 存储异步数据(响应式)
const userList = ref([])

// 异步请求数据
onMounted(async () => {
  const res = await axios.get('/api/users')
  userList.value = res.data
})

// Computed 依赖异步获取的 userList(数据更新后自动重新计算)
const activeUserCount = computed(() => {
  return userList.value.filter(user => user.status === 'active').length
})
</script>

4. Pinia 中的 Computed(状态派生)

Pinia 是 Vue3 官方状态管理库,其 getters 本质就是 Computed,支持依赖 Pinia 状态、其他 getters,且自带缓存:

javascript 复制代码
// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    users: [
      { id: 1, name: '张三', age: 20 },
      { id: 2, name: '李四', age: 25 }
    ]
  }),
  // getters = Pinia 中的 Computed
  getters: {
    // 1. 基础派生:统计成年用户
    adultUsers: (state) => {
      return state.users.filter(user => user.age >= 18)
    },
    // 2. 依赖其他 getters
    adultUserCount: (state, getters) => {
      return getters.adultUsers.length // 依赖 adultUsers
    },
    // 3. 接收参数(本质是返回函数的 Computed)
    getUserById: (state) => {
      // 返回函数,支持传参(但会失去缓存,每次调用都执行)
      return (id) => state.users.find(user => user.id === id)
    }
  }
})

在组件中使用:

vue 复制代码
<script setup>
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
console.log(userStore.adultUserCount) // 2
console.log(userStore.getUserById(1).name) // 张三
</script>

四、Vue3 Computed 避坑指南(重点!)

1. 忘记 .value 导致的错误(最常见)

Computed 返回的是 ref 对象,脚本中访问时必须加 .value,模板中会自动解包(无需加):

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const count = ref(1)
const double = computed(() => count.value * 2)

console.log(double) // ❌ 输出 RefImpl 对象(不是具体值)
console.log(double.value) // ✅ 输出 2
</script>

2. 依赖非响应式数据(无法触发更新)

Computed 仅能追踪 ref/reactive/Pinia 状态等「响应式数据」,非响应式数据(如普通变量、Math.random()Date.now())的变化不会触发重新计算:

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const count = ref(1)
const nonReactive = 2 // 非响应式变量

const badComputed = computed(() => {
  return count.value + nonReactive + Math.random()
})

count.value = 2 // 触发更新(count 是响应式)
nonReactive = 3 // 不触发更新(非响应式)
</script>
  • 修正:非响应式数据需用 ref 包装(const nonReactive = ref(2))。

3. 在 Computed 中修改响应式数据(无限循环)

Computed 的核心是「计算派生值」,而非「修改原始数据」,否则会导致依赖变化→Computed 重新执行→再次修改数据→无限循环:

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const count = ref(1)
const badComputed = computed(() => {
  count.value++ // ❌ 禁止在 Computed 中修改响应式数据
  return count.value * 2
})
</script>
  • 修正:修改数据应放在 watch、事件回调或 methods 中。

4. 复杂计算未拆分(可读性/性能差)

单个 Computed 函数包含多层逻辑(过滤+排序+格式转换)时,需拆分为多个简单 Computed,既提升可读性,又能利用缓存(中间结果复用):

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const goods = ref([/* 商品列表 */])

// 优化前:复杂逻辑聚合
const badComputed = computed(() => {
  return goods.value
    .filter(item => item.price > 1000)
    .sort((a, b) => b.price - a.price)
    .map(item => `【${item.name}】¥${item.price}`)
})

// 优化后:拆分多个 Computed
const expensiveGoods = computed(() => goods.value.filter(item => item.price > 1000))
const sortedGoods = computed(() => [...expensiveGoods.value].sort((a, b) => b.price - a.price))
const formattedGoods = computed(() => sortedGoods.value.map(item => `【${item.name}】¥${item.price}`))
</script>

5. 误解「可写 Computed」的使用场景

可写 Computed 的 set 方法必须反向修改原始依赖数据,否则修改 Computed 后不会同步到原始数据,导致状态不一致:

vue 复制代码
<script setup>
import { ref, computed } from 'vue'

const a = ref(1)
const b = ref(2)

// 错误:set 方法未修改原始依赖
const sum = computed({
  get() { return a.value + b.value },
  set(newValue) {
    console.log(newValue) // 仅打印,未修改 a/b
  }
})

sum.value = 5 // a 和 b 仍为 1 和 2,sum 下次访问时会恢复为 3
</script>

// 正确:set 方法修改原始依赖
const sum = computed({
  get() { return a.value + b.value },
  set(newValue) {
    a.value = newValue - b.value // 反向计算 a 的值
  }
})
sum.value = 5 // a 变为 3,sum 保持 5(a=3 + b=2)
</script>
相关推荐
Moment1 小时前
半年时间使用 Tiptap 开发一个和飞书差不多效果的协同文档 😍😍😍
前端·javascript·后端
前端加油站1 小时前
记一个前端导出excel受限问题
前端·javascript
da_vinci_x1 小时前
PS 生成式扩展:从 iPad 到带鱼屏,游戏立绘“全终端”适配流
前端·人工智能·游戏·ui·aigc·技术美术·游戏美术
一壶纱1 小时前
uni-app 中配置 UnoCSS
前端·vue.js
坐吃山猪1 小时前
Electron02-Hello
开发语言·javascript·ecmascript
步履不停_1 小时前
告别输入密码!打造基于 VS Code 的极致远程开发工作流
前端·visual studio code
狗哥哥1 小时前
Vue 3 企业级表格组件体系设计实战
前端
尘世中一位迷途小书童1 小时前
JavaScript 一些小特性:让你的代码更优雅高效
前端·javascript·架构
草帽lufei1 小时前
高强度SOLO真实业务项目
前端·ai编程·trae