深入 Vue3 computed

在模板里写一行 {{ sum }},背后却藏着「惰性求值 + 精准依赖收集 + 脏检查缓存」的三重魔法。本文带你钻进源码,拆解 computed 如何决定「何时算、何时不、为何不能异步」。

一、使用方法

ts 复制代码
const state = reactive({ a: 1, b: 2 })
const sum = computed(() => state.a + state.b)

console.log(sum.value) // 3
state.a = 10
console.log(sum.value) // 12

看似普通,但注意两点:

  • 只读:sum 不是函数,而是一个「带缓存的 getter」。
  • 懒执行:直到第一次读取 value,计算函数才真正跑一遍。

二、缓存机制:dirty 标志位

源码核心只有两行状态机:

ts 复制代码
let value: any
let dirty = true
  • dirty 为 true → 需要重新计算
  • dirty 为 false → 直接返回旧值

首次读取 sum.value 时,dirty 从 true 变为 false,并把结果存入 value

当依赖的响应式数据变化,调度器把 dirty 重新置为 true,但不会立即计算,而是等待下一次读取。

这就是「缓存」的本质:用 1 bit 的布尔值换一次昂贵的计算。

三、依赖收集:effect 包裹 getter

computed 的计算函数被 effect 包装成副作用:

ts 复制代码
const effectFn = effect(getter, {
  lazy: true,
  scheduler() {
    dirty = true
    trigger(obj, TriggerOpTypes.SET, 'value')
  }
})
  • lazy: true 阻止首次执行,实现惰性求值。
  • scheduler 在依赖变化时只打标记,不立即重算,确保缓存语义。

当模板读取 sum.valuetrack 把当前渲染副作用注册到 computed 的依赖图;当 state.a 变化,trigger 通知渲染器重新执行,渲染器再去读 sum.value,此时才真正触发计算。

四、为什么拒绝异步?

设想一个异步 computed:

ts 复制代码
const asyncSum = computed(async () => {
  const res = await fetch('/api/sum?a=' + state.a)
  return res.json()
})

问题立刻暴露:

  1. 缓存无法兑现

    第一次读取返回一个 Promise,第二次读取依赖并未变化,但缓存里存的是 Promise,无法直接返回结果。

  2. 渲染时数据缺位

    模板在渲染阶段需要同步值,异步导致视图出现空档或闪烁。

  3. 依赖追踪混乱

    异步完成时间不确定,期间若依赖再次变化,无法确定哪一次结果是最新。

Vue 官方给出的替代方案是 watch + ref

ts 复制代码
const asyncSum = ref(0)
watch(state, async () => {
  asyncSum.value = await fetch('/api/sum?a=' + state.a).then(r => r.json())
})

watch 不缓存、不阻塞渲染,天然适合异步副作用。

五、可写 computed:缓存 + setter 的双通道

ts 复制代码
const fullName = computed({
  get()   { return firstName.value + ' ' + lastName.value },
  set(v)  { [firstName.value, lastName.value] = v.split(' ') }
})
  • getter 走同样的缓存逻辑。
  • setter 只是普通函数,无缓存要求,因此可以包含异步(但仍不推荐,因为 setter 触发后 getter 需同步返回新值)。

总结

computed 用「dirty 位 + 惰性 effect」实现同步缓存,用「拒绝异步」换取数据一致性。理解了这一点,你就掌握了 Vue 性能调优的第一把钥匙。

相关推荐
倔强青铜三4 分钟前
苦练Python第46天:文件写入与上下文管理器
人工智能·python·面试
excel38 分钟前
Node.js 断言与测试框架示例对比
前端
天蓝色的鱼鱼2 小时前
前端开发者的组件设计之痛:为什么我的组件总是难以维护?
前端·react.js
codingandsleeping2 小时前
使用orval自动拉取swagger文档并生成ts接口
前端·javascript
石金龙3 小时前
[译] Composition in CSS
前端·css
白水清风3 小时前
微前端学习记录(qiankun、wujie、micro-app)
前端·javascript·前端工程化
Ticnix3 小时前
函数封装实现Echarts多表渲染/叠加渲染
前端·echarts
用户22152044278003 小时前
new、原型和原型链浅析
前端·javascript
阿星做前端3 小时前
coze源码解读: space develop 页面
前端·javascript
叫我小窝吧3 小时前
Promise 的使用
前端·javascript