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
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 nowRuns 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
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>>>
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
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