一、原理介绍

1.1 什么是computed

Vue 的核心能力是"数据驱动视图"——数据变了,视图自动更新。但视图不应该直接写数据,而应该从数据派生——因为派生关系意味着:

  1. 同一份数据可以派生多个视图(数据复用)
  2. 派生关系自动维护(数据变 → 所有派生值自动更新)
  3. 派生值可以被缓存(同一份数据不重复计算)

Vue 3 把这种派生能力封装成 computed(),这是 Vue 区别于 React 的关键设计之一。React 的派生要么写在 render 里(每次都执行)、要么用 useMemo(手动管理依赖),而 Vue 的 computed 自动追踪、自动缓存、自动失效。

1.2 响应式系统三件套

Vue 3的响应式系统由三部分组成:

1) reactive() / ref()                                       
用 Proxy 包装原始对象 / 值                              
拦截 get / set / has / deleteProperty 等操作            

(2) effect()                                                 
收集依赖:在函数执行时把"被读取的属性"登记到 dep        
触发更新:属性变化时把所有登记的 effect 重新执行        

(3)dep (依赖容器)                                           
订阅者列表,每个 reactive 属性都有独立的 dep            
 addSub / trigger / notify                              

简化版源码:

// reactive 用 Proxy 包装对象
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key)  // 收集依赖
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      Reflect.set(target, key, value)
      trigger(target, key)  // 触发更新
      return true
    }
  })
}

// track 把当前 effect 注册到属性的 dep
function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target)
    if (!depsMap) targetMap.set(target, depsMap = new Map())
    let dep = depsMap.get(key)
    if (!dep) depsMap.set(key, dep = new Set())
    dep.add(activeEffect)
  }
}

// trigger 把属性的所有 effect 重新执行
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) dep.forEach(effect => effect.run())
}

1.3 computed 的本质

computed() 本质上是一个带缓存的 effect

function computed(getter) {
  let cachedValue
  let dirty = true  // 标记脏值,需要重新计算

  const effect = createEffect(getter, () => {
    if (!dirty) {
      dirty = true
      // 让依赖自己的 effect 也失效
      trigger(computedObj, 'value')
    }
  })

  const computedObj = {
    get value() {
      track(computedObj, 'value')  // 谁读 computed 谁被收集
      if (dirty) {
        cachedValue = effect.run()
        dirty = false
      }
      return cachedValue
    },
    set value(newValue) {
      // 可选 setter
    }
  }

  return computedObj
}

三个关键特性:

  1. 懒计算:第一次访问 .value 才执行 getter
  2. 自动缓存:依赖没变就不重算
  3. 依赖追踪:谁读了 .value 谁就被登记为订阅者

1.4 依赖收集与缓存失效

模板渲染读取 .value
  ↓
activeEffect = 当前模板渲染 effect
  ↓
computed.get() 执行 getter
  ↓
getter 内部读取 reactive 属性(如 project.kcpNodes.kcp3.status)
  ↓
该属性的 get 被 Proxy 拦截
  ↓
track() 把当前 activeEffect 登记到该属性的 dep
  ↓
computed 缓存值,dirty = false
  ↓
下次访问 .value:如果 dirty = false 直接返回缓存

数据变化时:
  ↓
该属性的 set 被拦截
  ↓
trigger() 把 dep 里的 effect 重新执行
  ↓
computed 的 effect 标记 dirty = true,并 trigger 自己
  ↓
依赖 computed 的 effect(模板渲染)失效 → 下次渲染重新读 .value → 触发 getter 重算

关键点:computed 的依赖追踪是自动的——你写 getter 时正常读响应式数据就行,Vue 自动帮你记住"哪些属性变了会影响这个 computed"。


二、使用方法

2.1 基本语法

import { computed, ref } from 'vue'

const count = ref(0)

// 派生值:自动追踪、自动缓存
const double = computed(() => count.value * 2)

// 模板里访问(自动 unwrap)
console.log(double.value)  // 0
count.value = 5
console.log(double.value)  // 10(依赖变化触发重算)

2.2 computed 链:组合多个派生值

computed 之间可以互相依赖,形成派生链

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

// 第一层:基础派生
const sum = computed(() => a.value + b.value)

// 第二层:基于第一层再派生
const isPositive = computed(() => sum.value > 0)

// 第三层:基于第二层派生
const label = computed(() => isPositive.value ? '正数' : '非正数')

// a 变化 → sum 重算 → isPositive 重算 → label 重算
// b 变化同理
a.value = -1
console.log(label.value)  // '非正数'

响应式数据流

a / b 变化
   ↓
sum dirty = true
   ↓
谁读 sum(isPositive)触发重算
   ↓
isPositive dirty = true
   ↓
谁读 isPositive(label)触发重算
   ↓
label dirty = true
   ↓
模板渲染重读 label.value

2.3 计算属性的 Setter

computed 默认只有 getter,但可以加 setter 实现"双向绑定":

const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
  get: () => `${firstName.value}${lastName.value}`,
  set: (newValue) => {
    const parts = newValue.split('')
    firstName.value = parts[0]
    lastName.value = parts.slice(1).join('')
  }
})

fullName.value = '李四'
console.log(firstName.value)  // '李'
console.log(lastName.value)   // '四'

2.4 computed 与 watch 的边界

场景用 computed用 watch
派生一个值供模板/其他 computed 使用
监听某个值变化后执行副作用(API 调用 / DOM 操作)
同步派生(一个值变了立即重算)部分场景可
异步派生(debounce / API 调用)
派生值需要缓存✓(自动缓存)

关键原则

  • computed 用于派生值——纯函数,无副作用
  • watch 用于副作用——执行 API 调用 / 启动定时器 / 跳转路由

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐