computed 的缓存是怎么实现的?和 methods、watch 有什么区别?

先理解原理和示例,再练习完整口述与追问。

computed 是带脏标记的惰性副作用:首次读取时执行 getter 并收集依赖,依赖变化只把它标记为脏并通知订阅者,下次读取才重新计算,依赖不变就返回缓存。methods 每次调用都执行;watch 用于数据变化后执行副作用。Vue 3.4 起,computed 重新计算后值未变化,不会触发依赖它的副作用。

Vue3 中 computed 内部持有一个 ReactiveEffect,getter 运行期间读取的响应式数据把它登记为依赖。依赖变化时调度器不立即重算,而是设置 dirty 并触发读取过这个 computed 的副作用;这些副作用重新运行时读取 computed.value,发现 dirty 才执行 getter。这样多次修改依赖只计算一次,没人读取就不计算。

Vue 3.4 重构了响应式系统,computed 会比较新旧值,结果相同时不再通知下游,例如 count 从 2 变 4 而 isEven 一直是 true,依赖 isEven 的渲染不再重跑。Vue2 中 computed 基于 Watcher 的 lazy 与 dirty 实现,思路一致。

js
const items = ref([3, 8, 5])
const total = computed(() => items.value.reduce((sum, value) => sum + value, 0))
console.log(total.value) // 16,执行一次
console.log(total.value) // 16,读缓存
items.value.push(4)       // 标记为脏,不立即计算
console.log(total.value) // 20,重新计算

computed 的 getter 不应有副作用(发请求、修改其他状态);依赖的必须是响应式数据,Date.now() 这类非响应式值不会让它更新。可写 computed 的 setter 只用于转发修改,不应保存额外状态。

computed 是一个惰性的、带脏标记的副作用。第一次读取时执行 getter 并收集依赖,之后依赖变化只把它标记为脏并通知使用者,真正读取时才重新计算,依赖不变就直接返回缓存。methods 每次调用都会执行,watch 则用于在数据变化后做副作用,比如请求或存储。Vue 3.4 以后,computed 重算后值没变,还不会触发下游更新。