vue3-computed

📅 2026/8/3 7:44:50 👁️ 阅读次数 📝 编程学习
vue3-computed

computed(计算属性)是 Vue 3 中处理派生状态(Derived State)的核心利器。它的本质是“响应式数据的缓存计算器”

结合你已经学过的watchmethods,我用“Excel 单元格公式”来类比:原始数据是A1B1,计算属性就像=SUM(A1:B1)——只有当A1B1变了,求和结果才会重新计算;否则直接读缓存

下面我为你彻底拆解computed语法缓存机制watch/methods的本质区别,以及TypeScript 中的类型陷阱


1. 基础语法(只读计算属性)

在组合式 API(<script setup>)中,computed接收一个getter 函数,返回一个不可变的Ref对象

<script setup lang="ts"> import { ref, computed } from 'vue' // 原始数据 const firstName = ref('张') const lastName = ref('三') // 计算属性:返回全名 const fullName = computed(() => { return firstName.value + lastName.value }) // 模板中直接用,不需要 .value // 但在 JS 逻辑中访问需要 .value console.log(fullName.value) // 输出: "张三" </script> <template> <p>{{ fullName }}</p> <!-- 自动解包,显示 "张三" --> </template>

核心特性:只要firstNamelastName不变,多次访问fullName.value直接返回缓存结果,不会重新执行 getter 函数。


2. 🚀 进阶语法(可写计算属性)

虽然 90% 的场景计算属性是只读的,但你也可以定义一个setter,实现“修改派生值影响原始值”的效果(类似 Vuex 的commit)。

<script setup lang="ts"> const firstName = ref('张') const lastName = ref('三') const fullName = computed({ // getter:读取时触发 get: () => firstName.value + lastName.value, // setter:赋值时触发(比如 v-model 绑定) set: (newValue: string) => { const names = newValue.split(' ') firstName.value = names[0] || '' lastName.value = names[1] || '' } }) // 当执行 fullName.value = '李 四' 时,会触发 setter // 自动把 firstName 改为 '李',lastName 改为 '四' </script> <template> <!-- 双向绑定可写计算属性(非常优雅) --> <input v-model="fullName" /> </template>

警告:setter 里绝对不能修改计算属性本身(即不能fullName.value = xxx内部又触发 setter),这会造成死循环。


3.computedvsmethods(缓存机制的降维打击)

这是面试最常问的对比,我直接用代码演示性能差异

<script setup lang="ts"> import { ref, computed } from 'vue' const count = ref(1) // 1. 计算属性(有缓存) const computedDouble = computed(() => { console.log('计算属性重新计算了!') return count.value * 2 }) // 2. 方法(无缓存,每次调用都重新执行) const methodDouble = () => { console.log('方法重新执行了!') return count.value * 2 } </script> <template> <div> <!-- 在模板中调用 3 次计算属性 --> <p>{{ computedDouble }}</p> <!-- 打印 1 次 "计算属性重新计算了!" --> <p>{{ computedDouble }}</p> <!-- 直接读缓存,不打印 --> <p>{{ computedDouble }}</p> <!-- 直接读缓存,不打印 --> <!-- 在模板中调用 3 次方法 --> <p>{{ methodDouble() }}</p> <!-- 打印 1 次 --> <p>{{ methodDouble() }}</p> <!-- 打印 1 次 --> <p>{{ methodDouble() }}</p> <!-- 打印 1 次 --> <!-- 总共打印 3 次! --> </div> </template>

结论

  • methods:适合事件处理每次都需要重新计算的场景(如带参数的格式化)。
  • computed:适合依赖响应式数据计算开销较大的场景(如数组过滤、复杂数学计算)。

4.computedvswatch(本质不同)

对比维度computedwatch
定位派生新数据(输入 → 输出)执行副作用(输入 → 触发动作)
是否有返回值必须 return❌ 没有返回值(只执行逻辑)
缓存✅ 有缓存,依赖不变就不重新计算❌ 无缓存,只要数据变化就执行回调
典型场景过滤列表、格式化文本、拼接姓名发 API 请求、打印日志、操作 DOM

实战对比

// ✅ 用 computed:根据搜索词过滤列表(派生新数组) const filteredList = computed(() => { return list.value.filter(item => item.includes(searchKey.value)) }) // ✅ 用 watch:搜索词变了,去后端拉新数据(执行副作用) watch(searchKey, (newKey) => { fetchListByKey(newKey) // 发请求 })

5. TypeScript 类型标注(让 VSCode 精准提示)

computed在 TypeScript 中自动推导类型,但遇到复杂对象或联合类型时,需要显式标注。

① 自动推导(大多数情况够用)
const count = ref(1) const double = computed(() => count.value * 2) // double 被自动推断为 ComputedRef<number>
② 显式泛型(处理可能返回nullundefined的情况)
// 假设返回可能是 User 对象或 null const user = ref<User | null>(null) // 显式声明返回类型,避免 TS 报错 const displayName = computed<string>(() => { if (user.value) { return user.value.name } return '未登录' // 保证总是返回 string })
③ 可写计算属性的类型(Set 参数自动检查)
const fullName = computed<string>({ get: () => firstName.value + lastName.value, set: (newVal: string) => { // newVal 被限定为 string // ... } })

6. ⚠️ 致命陷阱(新手必踩的 3 个坑)

陷阱 1:在计算属性里修改其他响应式数据(副作用)

// ❌ 绝对禁止! const count = ref(0) const double = computed(() => { count.value++ // 在 getter 里改了别的响应式数据!会触发无限循环 return count.value * 2 })

原则:计算属性的 getter必须是无副作用的纯函数(只读,不写)。

陷阱 2:在模板中反复调用方法代替计算属性(导致性能浪费)

<!-- ❌ 不好:每次渲染都重新计算(且无法缓存) --> <p>{{ formatPrice(price) }}</p> <!-- ✅ 推荐:用计算属性自动缓存 --> <p>{{ formattedPrice }}</p>

陷阱 3:忘记.value(尤其在script中)

const fullName = computed(() => firstName.value + lastName.value) // ❌ 错误:fullName 本身是一个 Ref 对象 console.log(fullName) // 输出 RefImpl 对象 // ✅ 正确:必须取 .value console.log(fullName.value) // 输出 "张三"

7. 结合你之前的代码:给父组件添加计算属性

在你的父组件中,provideDatathemeversion,你可以派生一个新状态:

const provideData = reactive({ theme: 'dark', version: '1.0.0', }) // 派生一个显示用的主题描述(只读) const themeDescription = computed(() => { return provideData.theme === 'dark' ? '🌙 暗色模式' : '☀️ 亮色模式' })

然后在模板中用{{ themeDescription }}显示,当provideData.theme变化时,描述自动更新。


8. 性能优化进阶:computed的懒计算

computed懒执行的:

  • 即使依赖的数据变了,如果没有任何地方访问computed的值,它不会立即重新计算
  • 只有当你真正去读.value或模板渲染它时,它才会“迫不得已”重新计算。

这比watch更高效:watch是数据一变立即执行回调,不管你用不用结果。


总结记忆口诀

computed是“缓存计算器”,依赖不变不重算;
methods是“手动工具”,每次调用都重算;
watch是“监控雷达”,数据一变就触发动作。
Getter 必须纯Setter 别自循环

现在去你的项目里,找一个数组(比如userList),写一个computed来过滤出age > 18的用户列表,并尝试在模板中渲染。如果遇到 TS 类型报错,记得加上泛型computed<{...}[]>(() => ...)😄