Implementing Vue 3 Computed API from Scratch
This article explains how to recreate Vue 3's computed API using the composition API, covering getter/setter handling, effect integration, caching with a dirty flag, dependency tracking, and trigger mechanisms, and provides step‑by‑step code examples to build a functional ComputedRef implementation.
This article introduces a hands‑on implementation of Vue 3's computed API, starting with a brief reminder of the Vue 2 computed syntax and then showing the modern composition‑API usage.
In Vue 3, computed accepts either a getter function (read‑only) or an object containing get and set methods. The article demonstrates a simple example:
const count = ref(1)
const plusOne = computed(() => count.value + 1) // read‑only
console.log(plusOne.value) // 2To build our own computed , we first create a ComputedRefImpl class that holds a private _value , a getter , a setter , and an internal ReactiveEffect . The class exposes a value accessor that runs the effect and returns the cached result.
class ComputedRefImpl {
public readonly effect
private _value
private _dirty = true
constructor(getter, private readonly _setter) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerEffects(this.dep)
}
})
}
get value() {
if (this._dirty) {
this._dirty = false
this._value = this.effect.run()
}
return this._value
}
set value(newValue) {
this._setter(newValue)
}
}
export const computed = (getterOrOptions) => {
let onlyGetter = isFunction(getterOrOptions)
let getter, setter
if (onlyGetter) {
getter = getterOrOptions
setter = () => console.warn('no set')
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter)
}The implementation adds a _dirty flag to control caching: the getter recomputes only when dependencies change, otherwise it returns the previously stored value.
Dependency collection is achieved by tracking effects in a dep set. The trackEffects function records the active effect, while triggerEffects runs or schedules the stored effects when the underlying reactive values change.
export function track(target, type, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
trackEffects(dep)
}
export function trackEffects(dep) {
if (!activeEffect) return
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
export function trigger(target, type, key, value, oldValue) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
if (effects) {
triggerEffects(effects)
}
}
export function triggerEffects(effects) {
effects = [...effects]
effects.forEach(effect => {
if (effect !== activeEffect) {
if (effect.scheduler) effect.scheduler()
else effect.run()
}
})
}By integrating these pieces—effect, caching, and dependency tracking—the custom computed behaves like Vue's built‑in version, automatically updating when its reactive dependencies change.
The article concludes with a note that the real Vue source contains many more edge‑case considerations, and encourages readers to explore the official implementation for deeper understanding.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.