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
| Vue | Reflex Quant |
|---|---|
ref(x), r.value | signal(x), get(s) / set(s, v) |
computed(() => …); since 3.4 the getter receives the previous value | computed((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 reads | peek(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
- Failure. A computed that throws commits nothing, keeps its previous dependencies and runs again on its next demand.
- Change. Decided by
Object.isat commit; A → B → A counts as a change. - Reorders. Reading the same sources in another order moves relations and allocates nothing.
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-quantimport { 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 runsFAQ
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.