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

相关推荐
Hero_112720 小时前
在pycharm中install不上需要的包
服务器·前端·pycharm
爱上妖精的尾巴20 小时前
5-26 WPS JS宏数组元素添加删除应用
开发语言·前端·javascript·wps·js宏
是谁眉眼20 小时前
wpsapi
前端·javascript·html
谅望者20 小时前
Flexbox vs Grid:先学哪一个?CSS 布局完全指南(附可视化示例)
前端·css·html·css3·css布局·css flexbox·css grid
老华带你飞21 小时前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·商城推荐系统
JS.Huang21 小时前
【JavaScript】Pointer Events 与移动端交互
前端·javascript
一 乐21 小时前
物业管理系统|小区物业管理|基于SprinBoot+vue的小区物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端
H_HX12621 小时前
vue3 - 图片放大镜效果实现
前端·vue.js·vue3·vueuse·图片放大镜
yinuo1 天前
Git Submodule 与 Subtree 全方位对比:使用方式与场景选择
前端
yinuo1 天前
深入理解与实战 Git Subtree
前端