Vue · Reactivity

Reactivity

State is wrapped in a proxy that tracks every read and notifies every dependent on write. Change the value, and the DOM that used it re-renders itself. You never call setState.

01

ref()

the box

Wraps a value in an object with a .value accessor. The getter is where tracking hooks in, the setter is where triggering does — which is exactly why primitives need the box. You cannot intercept a read of a bare number.

const n = ref(0)
n.value++   // setter fires → trigger
02

reactive()

the proxy

Wraps an object in a Proxy: the get trap records who read each key, the set trap notifies them. Deep by default, nested objects proxied on access. Destructuring breaks the link — you walk away with the value, not the proxy.

const s = reactive({ n: 0 })
const { n } = s   // plain number now
03

effect()

the subscriber

Runs your function and, for the duration of that run, marks itself the active effect. Every reactive read while it runs registers it as a dependent. Change any of them and it runs again — collecting a fresh dependency set each time.

effect(() => console.log(s.n))
// logs now, and on every change to s.n
04

track / trigger

the bookkeeping

The wiring between the two halves. A WeakMap keyed by the raw object holds a Map of key to the set of effects that read it. track() adds the active effect on read; trigger() looks that set up on write and queues it.

WeakMap<target, Map<key, Set<effect>>>
05

computed()

the lazy effect

An effect that caches. A changed dependency only marks it dirty — nothing recomputes until something reads .value. It is a subscriber and a dependency at once, which is how one computed can depend on another.

const d = computed(() => n.value * 2)
d.value   // recomputes only if dirty
06

Scheduler & scope

the control

A trigger does not run effects on the spot — it hands them to a scheduler that dedupes and flushes once per tick, so a hundred writes cost one re-render. effectScope() groups effects so a component can stop all of its own on unmount.

const scope = effectScope()
scope.stop()   // dispose every effect