深入 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 性能调优的第一把钥匙。

相关推荐
程序员黑豆5 小时前
Java 注释详解:单行、多行与文档注释的完整指南
java·前端·ai编程
剪刀石头布啊6 小时前
antd中可编辑表格中巧用useWatch实现联动高性能效果,以及定制延伸学习
前端
油丶酸萝卜别吃6 小时前
前端转全栈学习路线
前端·学习
浮生望7 小时前
前端路由进阶:History API原理与手写HistoryRouter
前端
陆枫Larry7 小时前
JavaScript 中的竞态是什么,为啥会有竟态?
前端
上海安当技术9 小时前
半天接入:USBKey RESTful API + C 动态库,Web 和 C/S 两套集成路径实战
前端·后端·restful·集成·usbkey
DevUI团队9 小时前
从“即兴创作”到“规格先行”,华为云码道(CodeArts)代码智能体持续深耕企业级规范驱动开发能力
前端·人工智能·后端
weixin_4316004410 小时前
NestJS 入门(3):Guard 如何挡住未登录请求?
前端·后端·学习·nest.js
kyriewen11 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
IT_陈寒11 小时前
JavaScript类型转换把我坑惨了,这破玩意真该早点搞明白
前端·人工智能·后端