深入理解Vue3 computed
作者:前端小巷子
在模板里写一行 {{ sum }}
,背后却藏着「惰性求值 + 精准依赖收集 + 脏检查缓存」的三重魔法。本文带你钻进源码,拆解 computed 如何决定「何时算、何时不、为何不能异步」。
一、使用方法
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 标志位
源码核心只有两行状态机:
let value: any let dirty = true
- dirty 为 true → 需要重新计算
- dirty 为 false → 直接返回旧值
首次读取 sum.value
时,dirty 从 true 变为 false,并把结果存入 value
。
当依赖的响应式数据变化,调度器把 dirty 重新置为 true,但不会立即计算,而是等待下一次读取。
这就是「缓存」的本质:用 1 bit 的布尔值换一次昂贵的计算。
三、依赖收集:effect 包裹 getter
computed 的计算函数被 effect
包装成副作用:
const effectFn = effect(getter, { lazy: true, scheduler() { dirty = true trigger(obj, TriggerOpTypes.SET, 'value') } })
lazy: true
阻止首次执行,实现惰性求值。scheduler
在依赖变化时只打标记,不立即重算,确保缓存语义。
当模板读取 sum.value
,track
把当前渲染副作用注册到 computed 的依赖图;当 state.a
变化,trigger
通知渲染器重新执行,渲染器再去读 sum.value
,此时才真正触发计算。
四、为什么拒绝异步?
设想一个异步 computed:
const asyncSum = computed(async () => { const res = await fetch('/api/sum?a=' + state.a) return res.json() })
问题立刻暴露:
缓存无法兑现
第一次读取返回一个
Promise
,第二次读取依赖并未变化,但缓存里存的是 Promise,无法直接返回结果。渲染时数据缺位
模板在渲染阶段需要同步值,异步导致视图出现空档或闪烁。
依赖追踪混乱
异步完成时间不确定,期间若依赖再次变化,无法确定哪一次结果是最新。
Vue 官方给出的替代方案是 watch
+ ref
:
const asyncSum = ref(0) watch(state, async () => { asyncSum.value = await fetch('/api/sum?a=' + state.a).then(r => r.json()) })
watch 不缓存、不阻塞渲染,天然适合异步副作用。
五、可写 computed:缓存 + setter 的双通道
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 性能调优的第一把钥匙。
到此这篇关于深入理解Vue3 computed的文章就介绍到这了,更多相关Vue3 computed内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!