Vue Reactivity vs Signals: ref, computed, effect and the Reactive Graph

Vue reactivity and signal runtimes share a dependency-graph core; Vue additionally tracks object properties through proxies. Underneath the different spelling it is the same idea. The differences are in what each one decides for you.

This page compares models, not speed: Quant has not been benchmarked against @vue/reactivity, and it is not a drop-in replacement for it. Vue's reactivity is one part of a framework with a renderer and a component model. Quant is the graph alone.

Concept map

VueReflex Quant
ref(x), r.valuesignal(x), get(s) / set(s, v)
computed(() => …); since 3.4 the getter receives the previous valuecomputed((prev) => …, initial?); prev is the last committed value
watchEffect(fn)effect(fn)
reactive(obj) (deep Proxy)no equivalent: compose signals explicitly
effectScope()no equivalent; dispose(node) for one node
untracked readspeek(x), untracked(fn)

Tracking: properties or calls

Vue can make a plain object reactive: a Proxy intercepts property access, so state.user.name is tracked without you naming a signal. That is the convenience Vue is known for, and it has a price in identity (the proxy is not the original object) and in knowing what exactly was tracked.

Quant tracks one thing: a call to get. There is no deep reactivity and nothing implicit. A dependency exists because a line of code asked for it, and that is the whole rule. The granularity is whatever you make a signal.

What a computed may do

Vue's documentation is direct about it: a computed getter should only perform pure computation and be free of side effects, and should not mutate other state. Derived values are derived; changing state belongs in watchers.

Quant does not draw that line in the same place. A computation may write, and the contract says what follows: a source that changes after the computation read it leaves the computation owing another run, and an effect invalidated during its own run is delivered again. That turns a computed with a previous value into something Vue's model asks you to build from two parts: a state transition that keeps its own state, takes causes in through reads and sends consequences out through writes. Fireflies and Constraints are built on exactly that.

The permission is not an invitation. A demand that reaches a computation whose own evaluation is in progress is a broken precondition, and the development build reports it as a cycle.

Scheduling and ownership

Vue schedules. Its watchers are tied to the component update cycle, and effectScope collects the effects created inside it so they can be stopped together. Those are framework decisions, made well, for a framework.

Quant makes neither. Its host runs effects in a synchronous drain, batch defers that drain and nothing else, and there is no scope: one node is disposed at a time. When a watcher runs, and who owns a group of watchers, are left to whatever sits above the runtime.

What is written down

All of it is in CONTRACT.md and tested differentially against an executable specification. This page does not claim Vue behaves differently in these cases; only that in Quant they are specified.

Which one

Inside a Vue application, Vue's reactivity: the renderer tracks Vue's refs, and nothing else will re-render a template. Outside one, for plain state in a library, a worker or another framework, a signals runtime of about 2.2 KB with no rendering assumptions is the smaller dependency and the more explicit one.

Try it

npm install reflex-quant
import { computed, effect, get, set, signal } from "reflex-quant";

const full = signal(false);
const first = signal("Ada");
const last = signal("Lovelace");
const name = computed(() => (get(full) ? `${get(first)} ${get(last)}` : get(first)));

effect(() => console.log(get(name))); // "Ada"        name reads full, first
set(last, "Byron");                   // nothing runs: last was not read
set(full, true);                      // "Ada Byron"  name now reads last too
set(full, false);                     // "Ada"        last is dropped again
set(last, "King");                    // nothing runs
runs reflex-quant 1.1.2 in your browser

FAQ

Can I use Reflex Quant instead of Vue's reactivity?

Outside Vue components, for plain state, yes: ref maps to signal, computed to computed, watchEffect to effect. Inside Vue templates, Vue's own refs are what the renderer tracks.