Frontend Development 10 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing Vue 3 Computed API from Scratch

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) // 2

To 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.

frontendJavaScriptVueComposition APIReactivitycomputed
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.