Skip to content

computed 计算属性

computed() 接受一个 getter 函数,返回一个只读的响应式 ref。计算属性会缓存结果,只有依赖的响应式数据变化时才重新计算——这是它与普通方法的核心区别。

速查卡片

内容
签名computed(getter)computed({ get, set })
入参getter 函数 (oldValue?) => T,或 { get, set } 对象
返回值只读模式:Readonly<Ref<T>>;可写模式:Ref<T>
缓存✅ 依赖不变不重新计算
懒执行✅ 首次访问时才计算(未被访问永不执行)
可写✅ 提供 get + set 即可
调试onTrack / onTrigger(3.2+)
版本≥ 3.0.0
场景推荐
从已有响应式数据派生新值computed
监听数据变化并执行副作用(API 调用、DOM 操作)watch / watchEffect
简单表达式模板内直接写
需要 DOM 更新后执行watch + nextTickwatchEffect({ flush: 'post' })

概述

在模板中写复杂表达式会让模板臃肿难维护。计算属性将逻辑抽离到纯函数中,模板只负责展示:

vue
<script setup lang="ts">
import { computed, ref } from 'vue'

const items = ref([1, 2, 3, 4, 5])
const evenItems = computed(() => items.value.filter(i => i % 2 === 0))
</script>

<template>
  <ul>
    <li v-for="item in evenItems" :key="item">{{ item }}</li>
  </ul>
</template>

evenItems 只在 items 变化时重新计算,其余时间返回缓存值。

基础概念

缓存机制——computed vs method 的核心区别

vue
<script setup lang="ts">
import { computed, ref } from 'vue'

const count = ref(0)

// 计算属性——有缓存
const computedNow = computed(() => {
  console.log('computed 执行')
  return Date.now()
})

// 普通方法——无缓存
function methodNow() {
  console.log('method 执行')
  return Date.now()
}
</script>

<template>
  <p>computed: {{ computedNow }}</p>   <!-- 只执行一次,后续复用缓存 -->
  <p>computed: {{ computedNow }}</p>   <!-- 同上,不重新计算 -->
  <p>method: {{ methodNow() }}</p>     <!-- 每次访问都执行 -->
  <p>method: {{ methodNow() }}</p>     <!-- 同上 -->
</template>

计算属性的缓存依赖响应式追踪:只有其 getter 中实际读取过的响应式数据发生变化时,才标记缓存失效。如果依赖的数据没变,即使组件重新渲染,计算属性也不会重新计算。

懒计算

计算属性在首次被访问时才执行 getter。如果从未在模板或 JS 中读取,计算逻辑永不触发。这和 watchEffect 的立即执行完全不同。

基础用法

示例一:最简——派生布尔值

vue
<script setup lang="ts">
import { ref, computed } from 'vue'

const count = ref(0)
const isEven = computed(() => count.value % 2 === 0)
</script>

<template>
  <p>count: {{ count }}</p>
  <p>{{ isEven ? '偶数' : '奇数' }}</p>
  <button @click="count++">+1</button>
</template>

示例二:典型——搜索过滤

vue
<script setup lang="ts">
import { ref, computed } from 'vue'

interface User {
  id: number
  name: string
}

const users = ref<User[]>([
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' },
])
const query = ref('')

const filteredUsers = computed(() =>
  users.value.filter(u => u.name.toLowerCase().includes(query.value.toLowerCase()))
)
</script>

<template>
  <input v-model="query" placeholder="搜索..." />
  <ul>
    <li v-for="u in filteredUsers" :key="u.id">{{ u.name }}</li>
  </ul>
</template>

示例三:进阶——可写计算属性(v-model 绑定)

vue
<script setup lang="ts">
import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

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

<template>
  <input v-model="fullName" placeholder="全名" />
  <p>姓: {{ firstName }}</p>
  <p>名: {{ lastName }}</p>
</template>

可写 computed 常用于封装多个响应式数据为一个逻辑单元,外部通过 v-model 无缝操作。

示例四:冷门——调试模式(onTrack / onTrigger)

Vue 3.2 起支持在 computed 中注入调试钩子,追踪依赖的收集和触发过程:

vue
<script setup lang="ts">
import { ref, computed } from 'vue'

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

const sum = computed(() => a.value + b.value, {
  onTrack(e) {
    // 当 a.value 或 b.value 的依赖被首次追踪时触发
    console.log('track', e)
  },
  onTrigger(e) {
    // 当 a.value 或 b.value 变化导致重新计算时触发
    console.log('trigger', e)
  },
})
</script>

调试钩子仅在开发模式生效。还可配合 computedoldValue 参数做缓存穿透检测:

typescript
// 利用 oldValue 做自定义缓存判断(3.4+ 稳定性优化)
import { computed, ref } from 'vue'

const items = ref([{ id: 1, value: 10 }, { id: 2, value: 20 }])
const total = computed((oldTotal) => {
  const newTotal = items.value.reduce((sum, i) => sum + i.value, 0)
  // 值没变则返回旧对象引用,避免下游不必要的更新
  return newTotal === oldTotal?.value ? oldTotal : newTotal
})

API 参考

computed() — 只读模式

内容
签名function computed<T>(getter: (oldValue: T | undefined) => T, debuggerOptions?: DebuggerOptions): Readonly<Ref<Readonly<T>>>
入参getter: 计算函数,接收上一次返回值(首次为 undefined
返回值Readonly<Ref<T>>,只能读 .value,不可写入
版本≥ 3.0.0;oldValue 参数 ≥ 3.4.0;debuggerOptions ≥ 3.2.0

computed() — 可写模式

内容
签名function computed<T>(options: { get: (oldValue: T | undefined) => T; set: (value: T) => void }, debuggerOptions?: DebuggerOptions): Ref<T>
入参get: 读取时的计算函数;set: 写入时的反向解析函数
返回值Ref<T>,可读写 .value
版本≥ 3.0.0

DebuggerOptions

属性签名说明
onTrack(e: DebuggerEvent) => void响应式依赖被追踪时调用
onTrigger(e: DebuggerEvent) => void依赖变化触发重新计算时调用
typescript
interface DebuggerEvent {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes | TriggerOpTypes
  key: any
}

类型定义

typescript
// 只读
function computed<T>(
  getter: (oldValue: T | undefined) => T,
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写
function computed<T>(
  options: {
    get: (oldValue: T | undefined) => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>

// 调试事件
interface DebuggerEvent {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes | TriggerOpTypes
  key: any
}

interface DebuggerOptions {
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

注意 TypeScript 不会自动推断 computed 的泛型,但通常可以从 getter 返回值推断。如果推断失败(如使用三元表达式),显式标注:computed<string>(() => ...)

computed vs watch 决策指南

需求computedwatch
从已有数据派生新值
执行副作用(API 调用)
自动追踪依赖✅(watchEffect
懒执行(首次不跑)✅(watch 默认懒)
可写(反向赋值)
有返回值
访问旧值✅(oldValue 参数)✅((newVal, oldVal)
异步操作
DOM 更新后回调✅({ flush: 'post' }

经验法则:如果需要"产生一个值"就选 computed,需要"做一件事"就选 watch。

边界情况与注意事项

依赖未使用时不会缓存失效

typescript
const a = ref(1)
const b = ref(2)
const sum = computed(() => a.value + b.value)
// 修改 c 不会触发 sum 重算——因为 sum 的 getter 没读 c
// 修改 b 会触发——因为 getter 中读取了 b.value

每次创建新对象会导致缓存"失效"

typescript
// ❌ 每次返回新数组引用,下游 effect 总会触发
const filtered = computed(() => items.value.filter(i => i.active))

// ✅ 3.4+ 优化:值浅比较相同则复用旧值
// 但如果 filter 结果不同,仍然触发(正确行为)

3.4 起 Vue 对 computed 值做浅比较,值不变不触发副作用。但若结果始终是新对象引用(如 map 返回新数组),下游仍会触发。优化方案:

typescript
const filtered = computed((oldVal) => {
  const newVal = items.value.filter(i => i.active)
  // 手动深比较,相同时返回旧引用
  return isEqual(newVal, oldVal) ? oldVal : newVal
})

不要在 computed 中执行副作用

typescript
// ❌ computed 中发起请求——不会按预期触发
const data = computed(() => {
  fetch('/api/data').then(r => r.json())
  return null
})

// ✅ 用 watch 或 watchEffect
watchEffect(() => {
  fetch('/api/data').then(r => response.value = r.json())
})

非响应式依赖不会触发重算

typescript
let external = 1
const doubled = computed(() => external * 2)
external = 5
console.log(doubled.value) // 仍是 2!external 不是响应式的

循环依赖

typescript
const a = ref(1)
const b = computed(() => a.value + 1)
// ❌ 不要这样做:a = computed(() => b.value + 1)  会导致无限循环

常见误区

  1. computed 可以替代所有模板表达式 → 简单表达式(a + b)直接写在模板更简洁,过度 computed 反而增加心智负担。

  2. computed 会"主动推送"更新 → 不。它是 pull-based(拉取模式):仅在被访问时才计算。未被访问的 computed 永不执行。

  3. computed 值每次访问都重新计算 → 错。有缓存,依赖不变返回上一次的结果。

  4. 可以在 computed 中执行异步操作 → 不能。computed 必须是同步纯函数。异步逻辑用 watch

  5. computed 依赖会自动解包 .value → 不。getter 中访问 ref 必须写 .value,computed 本身不会自动解包依赖。

  6. 可写 computed 很少用 → 可写 computed 在表单双向绑定场景非常有用,封装一组相关状态为一个逻辑单元。

  7. 修改 computed 依赖会立即触发重算 → 不。computed 是懒计算,依赖变化只标记缓存失效,下次读取时才真正重算。

版本信息

版本变更
3.0.0computed() 首次发布(只读 + 可写模式)
3.2.0新增 onTrack / onTrigger 调试钩子
3.4.0Computed Stability:值浅比较不变时不触发副作用;oldValue 参数支持

来源

来源链接
官方指南 — Computed Propertiesvuejs.org/guide/essentials/computed
官方 API — computed()vuejs.org/api/reactivity-core#computed
性能指南 — Computed Stabilityvuejs.org/guide/best-practices/performance#computed-stability
官方指南 — Computed Debuggingvuejs.org/guide/extras/reactivity-in-depth#computed-debugging

最后整理:2026-07-05 · 对应 Vue 版本:3.5.x