Vue.js: 计算属性 computed

最后更新:2026-08-26

计算属性(computed)是 Vue 3 中基于响应式数据自动重算的特殊属性。它的核心价值是缓存——只有依赖的数据变化时才重新计算,否则直接返回上次的值。理解 computed 是写出高性能 Vue 应用的关键。

计算属性的核心是 3 个特性:自动追踪依赖 + 缓存结果 + 响应式。本课帮你掌握它的 5 种用法和 3 个陷阱。

1. 你将学到


2. 一个购物车总价的性能问题

(1) 痛点:每次点击都重新算 100 个商品

Alice 在电商后台实现了购物车总价计算:

JS
// ❌ 翻车版:用 methods
function cartTotal(items) {
  console.log('Calculating...')  // 每次调用都打印
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
HTML
<template>
  <div>
    <p>Total: ${{ cartTotal(items) }}</p>
    <p>Tax: ${{ cartTotal(items) * 0.08 }}</p>
    <p>Grand Total: ${{ cartTotal(items) * 1.08 }}</p>
    <button @click="showDate = new Date()">Update Time</button>
  </div>
</template>

性能极差:

性能监控工具显示:

"cartTotal 函数每次页面加载调用 5,000 次。100 个商品 × 5,000 = 500,000 次逐项计算。3 秒卡顿。"

(2) Vue computed 解法:缓存 + 依赖追踪

JS
import { ref, computed } from 'vue'

const items = ref([
  { price: 100, quantity: 2 },
  { price: 50, quantity: 1 }
])

// ✅ computed:只依赖 items,items 不变就不重算
const cartTotal = computed(() => {
  console.log('Calculating...')  // 只在 items 变化时打印
  return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})

// computed 自动处理 3 处使用:只算 1 次
const tax = computed(() => cartTotal.value * 0.08)
const grandTotal = computed(() => cartTotal.value * 1.08)

(3) 收益

切换到 computed 后:


3. computed() 基本用法

(1) 标准语法

JS
import { ref, computed } from 'vue'

const count = ref(0)

// 基础计算属性
const doubleCount = computed(() => {
  return count.value * 2
})

// 简写
const doubleCount = computed(() => count.value * 2)

// 使用
console.log(doubleCount.value)  // 0
count.value = 5
console.log(doubleCount.value)  // 10

(2) 模板中使用

VUE
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="count++">+1</button>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>

模板中 {{ doubleCount }} 不需要 .value(自动解包)。

(3) 5 大特性

100%
graph TB
    subgraph Computed核心特性
        A[自动追踪依赖]
        B[缓存结果]
        C[响应式]
        D[懒计算]
        E[可链式调用]
    end
    
    style A fill:#42b883,color:#fff
    style B fill:#42b883,color:#fff
    style C fill:#42b883,color:#fff
    style D fill:#42b883,color:#fff
    style E fill:#42b883,color:#fff
特性 说明
自动追踪依赖 自动检测函数体中用到的响应式数据
缓存结果 依赖不变时多次访问只算 1 次
响应式 依赖变化时自动重新计算
懒计算 不被访问就不算(节省性能)
可链式 computed 可以依赖其他 computed

4. computed vs methods 5 大区别

维度 computed methods
缓存 ✅ 依赖不变就不重算 ❌ 每次调用都重算
响应式 ✅ 自动追踪依赖 ❌ 不追踪(要手动传参)
模板访问 {{ doubleCount }} {{ doubleCount() }}
参数 ❌ 不支持 ✅ 接受任意参数
适用场景 简单派生值 复杂逻辑、事件处理

(4) 5 个选择场景

场景 用 computed 用 methods
显示数字翻倍
计算购物车总价
处理按钮点击
发送 API 请求
格式化日期(无依赖)
过滤列表(依赖 data)
表单校验

5. getter/setter 双向计算属性

(1) 默认只有 getter

JS
const firstName = ref('Alice')
const lastName = ref('Smith')

// 只有一个 getter
const fullName = computed(() => `${firstName.value} ${lastName.value}`)

console.log(fullName.value)  // Alice Smith
// fullName.value = 'Bob Lee'  // ❌ 警告:计算属性是只读的

(2) 完整 getter + setter

JS
const firstName = ref('Alice')
const lastName = ref('Smith')

const fullName = computed({
  // 读取:拼起来
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  // 写入:拆开
  set(newValue) {
    const parts = newValue.split(' ')
    firstName.value = parts[0]
    lastName.value = parts[1] || ''
  }
})

// ✅ 现在可读可写
fullName.value = 'Bob Lee'
console.log(firstName.value)  // Bob
console.log(lastName.value)   // Lee

(3) 模板中双向绑定

VUE
<template>
  <div>
    <input v-model="fullName">
    <p>First: {{ firstName }}</p>
    <p>Last: {{ lastName }}</p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const firstName = ref('Alice')
const lastName = ref('Smith')

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    const parts = val.split(' ')
    firstName.value = parts[0]
    lastName.value = parts[1] || ''
  }
})

6. 计算属性链式调用

(1) 嵌套 computed

JS
const items = ref([
  { price: 100, quantity: 2 },
  { price: 50, quantity: 3 }
])

// 第一层:原价合计
const subtotal = computed(() =>
  items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)

// 第二层:税(基于 subtotal)
const tax = computed(() => subtotal.value * 0.08)

// 第三层:折扣(基于 subtotal)
const discount = computed(() => subtotal.value > 200 ? 20 : 0)

// 第四层:最终总价
const total = computed(() => subtotal.value + tax.value - discount.value)

console.log(total.value)  // 350 * 1.08 - 20 = 358

(2) 链式 computed 缓存机制

100%
graph TB
    A[items change] --> B[subtotal 重新计算]
    B --> C[tax 重新计算]
    B --> D[discount 重新计算]
    C --> E[total 重新计算]
    D --> E
    
    style A fill:#42b883
    style B fill:#42b883
    style E fill:#42b883

只有依赖链上的 computed 会重算,未被使用的不会算。


7. 完整示例:电商购物车总价

▶ 示例:基础 computed

HTML
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<div id="app">
  <p>Subtotal: ${{ subtotal }}</p>
  <p>Tax (8%): ${{ tax }}</p>
  <p>Discount: -${{ discount }}</p>
  <p><strong>Total: ${{ total }}</strong></p>
  <button @click="addItem">+ 加 1 件 Case</button>
</div>

<script>
const { createApp, ref, computed } = Vue
createApp({
  setup() {
    const items = ref([
      { name: 'iPhone', price: 999, quantity: 1 },
      { name: 'Case', price: 50, quantity: 2 }
    ])

    const subtotal = computed(() =>
      items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
    )
    const tax = computed(() => subtotal.value * 0.08)
    const discount = computed(() => subtotal.value > 1000 ? 50 : 0)
    const total = computed(() => subtotal.value + tax.value - discount.value)

    function addItem() {
      items.value.push({ name: 'Case', price: 50, quantity: 1 })
    }

    return { subtotal, tax, discount, total, addItem }
  }
}).mount('#app')
</script>
▶ 试一试

▶ 示例:getter + setter

HTML
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<div id="app">
  <input v-model="fullName" placeholder="First Last">
  <p>First: {{ firstName }}</p>
  <p>Last: {{ lastName }}</p>
</div>

<script>
const { createApp, ref, computed } = Vue
createApp({
  setup() {
    const firstName = ref('Alice')
    const lastName = ref('Smith')

    const fullName = computed({
      get: () => `${firstName.value} ${lastName.value}`,
      set: (val) => {
        const parts = val.split(' ')
        firstName.value = parts[0] || ''
        lastName.value = parts[1] || ''
      }
    })
    return { firstName, lastName, fullName }
  }
}).mount('#app')
</script>
▶ 试一试

▶ 示例:computed 链式调用

HTML
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<div id="app">
  <p>基础分: {{ baseScore }}</p>
  <p>附加分: {{ difficultyBonus }}</p>
  <p>总分: {{ baseScore + difficultyBonus }}</p>
  <p>等级: {{ grade }}</p>
  <button @click="score += 5">+5 分</button>
</div>

<script>
const { createApp, ref, computed } = Vue
createApp({
  setup() {
    const score = ref(85)

    // 第一层:基础分
    const baseScore = computed(() => score.value)

    // 第二层:附加分
    const difficultyBonus = computed(() => {
      if (baseScore.value >= 90) return 10
      if (baseScore.value >= 80) return 5
      return 0
    })

    // 第三层:等级
    const grade = computed(() => {
      const total = baseScore.value + difficultyBonus.value
      if (total >= 95) return 'A+'
      if (total >= 90) return 'A'
      if (total >= 80) return 'B'
      return 'C'
    })

    return { score, baseScore, difficultyBonus, grade }
  }
}).mount('#app')
</script>
▶ 试一试

▶ 示例:computed vs methods 对比

HTML
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<div id="app">
  <p>Computed (1 次): {{ totalComputed }} | {{ totalComputed }} | {{ totalComputed }}</p>
  <p>Methods (3 次): {{ getTotal() }} | {{ getTotal() }} | {{ getTotal() }}</p>
</div>

<script>
const { createApp, ref, computed } = Vue
createApp({
  setup() {
    const items = ref([
      { name: 'A', price: 100, quantity: 1 },
      { name: 'B', price: 50, quantity: 2 }
    ])

    // ❌ methods:每次调用都重算
    function getTotal() {
      console.log('methods called')   // 模板中多次使用会打印多次
      return items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
    }

    // ✅ computed:只算 1 次
    const totalComputed = computed(() => {
      console.log('computed called')  // 只打印 1 次
      return items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
    })

    return { items, getTotal, totalComputed }
  }
}).mount('#app')
</script>
▶ 试一试

▶ 示例:5 个性能对比场景

场景 methods 调用次数 computed 调用次数
模板中 1 处使用 1 1
模板中 3 处使用 3 1
模板中 10 处使用 10 1
父组件更新但 items 不变 重新调用 不重算
依赖变化 重新调用 重新计算

▶ 示例:5 个常见错误

错误 现象 解决
用 methods 替代 computed 多次使用性能差 改用 computed
误给 computed 传参 报错 computed 不支持参数,用 methods
setter 写自己 无限循环 setter 改依赖,不要 fullName.value = ...
循环依赖 A→B→A 死循环 拆掉其中一个依赖
复杂逻辑写在 computed 难调试 拆出方法,computed 调方法

❓ 常见问题

Q computed 和 methods 到底用哪个?
A 能用 computed 就用 computed(缓存 + 自动追踪)。只有以下情况用 methods:(1) 需要传参;(2) 不依赖响应式数据;(3) 事件处理器(@click 等)。
Q computed 能传参吗?
A 不能。computed 只能有 0 个参数。如果需要传参,用 methods 替代:getDouble(n) { return n * 2 },或用 computed 返回一个函数:computed(() => (n) => n * 2)
Q computed 在 setup 外能访问吗?
A 能。computed 返回的是 ref 对象,可以传给其他组件或函数。但要保持响应式,建议把 computed 当作"派生 ref"用。
Q computed 和 watch 有什么区别?
A computed 是"派生值"(基于数据计算新值),watch 是"监听器"(数据变化时执行副作用)。computed 适合纯计算,watch 适合发请求、设状态等。
Q computed 性能比 methods 好在哪?
A 缓存。methods 每次调用都执行函数,computed 只在依赖变化时执行。模板中 3 处用 computed,只算 1 次;用 methods 算 3 次。
Q setter 怎么写才不无限循环?
A setter 中只改 computed 的依赖(ref),不要赋值给 computed 自己。例如 fullName 的 setter 应该改 firstName / lastName,不是 fullName.value = ...
Q computed 可以异步吗?
A 不能。computed 是同步计算,异步用 watch 或 watchEffect。如果需要"异步计算属性",用 data() { return { result: null } } + watch + onMounted 组合。

📖 小节


📝 作业

  1. 基础题(难度⭐) 写一个计算属性 wordCount,统计一段文本的单词数(用 split 拆分空格)。

    数据:const text = ref('Hello Vue 3 from Alice') 应输出 4。

  2. 进阶题(难度⭐⭐) 写一个购物车总价计算,包含 3 个计算属性:

    • subtotal:所有商品 price × quantity 之和
    • discount:满 100 减 10,满 200 减 30
    • total:subtotal - discount
    • 3 个商品:iPhone $999 × 1, Case $50 × 2, Charger $30 × 1
  3. 挑战题(难度⭐⭐⭐) 实现一个完整的"购物车总价系统":

    1. 5 个商品,每个含 name/price/quantity
    2. subtotal / tax(8%) / discount(满 200 减 20) / total 4 个计算属性
    3. fullName 计算属性用 getter/setter,setter 中根据输入更新 firstName/lastName
    4. 测试 5 个计算属性的依赖追踪(修改任意商品后哪些会重算)
    5. 用 mermaid 画出计算属性依赖图
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏