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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
