Why Frameworks Exist contrasted the two models; here is the machinery. A virtual DOM renderer re-runs the component to get a new tree of plain objects and diffs it against the old one. A general tree diff is O(n³); React 7,897 's reconciler is O(n) thanks to two heuristics. An element whose type changed (<div> to <section>, or one component to another) is rebuilt, losing its state. Children are matched by key, or by position without keys, so inserting at the top of an unkeyed list rewrites every row: the classic answer to "why does React need keys?" Vue 3 5,482 's compiler adds hints: static nodes are cached, and patch flags such as 2 /* CLASS */ tell the diff what may change.
Signals skip the diff: a signal records which running computation reads it and re-runs only those.
let running = null; // the effect that is executing right now
function signal(value) {
const subscribers = new Set();
const read = () => { if (running) subscribers.add(running); return value; };
const write = (v) => { if (v !== value) { value = v; subscribers.forEach((f) => f()); } };
return [read, write];
}
function effect(fn) {
const run = () => { const outer = running; running = run; fn(); running = outer; };
run();
}
const [count, setCount] = signal(0), [user, setUser] = signal('Ada');
effect(() => console.log(`text node = ${count()}`)); // roughly what Solid makes of {count()}
effect(() => console.log(`title attribute = ${user()}`));
setCount(1); setCount(1); setUser('Grace');text node = 0 title attribute = Ada text node = 1 title attribute = Grace
The repeated setCount(1) did nothing, and setUser never touched the count. A Solid 269,718 component runs once; then effects like these patch single nodes. Built-in signals are a Stage 1 TC39 proposal (github.com/tc39/proposal-signals (https://github.com/tc39/proposal-signals 4,191 )).