JavaScript Signals: The Future of Reactivity in the Browser
Explore the TC39 Signals proposal and how it unifies reactivity across frameworks like Angular, Vue, and Solid.
What Are Signals?
Signals are a reactive primitive — a value container that automatically tracks dependencies and notifies consumers when the value changes. If you've used Vue's \ref()\, Solid's \createSignal()\, or Angular's \signal()\, you already know the concept. The TC39 Signals proposal aims to standardize this pattern at the language level.
The core idea: instead of manually subscribing to changes or diffing virtual DOMs, signals create a dependency graph that automatically updates only what's needed.
The Core API
``javascript
// Signal.State — a reactive value
const count = new Signal.State(0);
console.log(count.get()); // 0
count.set(5); console.log(count.get()); // 5
// Signal.Computed — derived values that auto-track dependencies const doubled = new Signal.Computed(() => count.get() * 2); console.log(doubled.get()); // 10
count.set(10);
console.log(doubled.get()); // 20 — automatically updated
`
Building a Reactive System
`javascript
// A simple effect system built on signals
function effect(fn) {
const computed = new Signal.Computed(fn);
// Use a watcher to re-run when dependencies change const watcher = new Signal.subtle.Watcher(() => { queueMicrotask(() => { watcher.watch(computed); computed.get(); }); });
watcher.watch(computed); computed.get();
return () => watcher.unwatch(computed); }
// Usage
const name = new Signal.State('Alice');
const greeting = new Signal.Computed(() => \Hello, \${name.get()}!\);
const cleanup = effect(() => { console.log(greeting.get()); }); // Logs: "Hello, Alice!"
name.set('Bob');
// Logs: "Hello, Bob!" — automatically
`
Why Signals Over Other Patterns?
Signals solve problems that other reactivity approaches struggle with:
vs Event Emitters: Signals are pull-based — consumers request values rather than being pushed to. This avoids glitches where intermediate states are observed.
vs Virtual DOM diffing: Signals provide fine-grained updates. Only the exact bindings that depend on a changed signal update — no tree diffing needed.
vs Observables (RxJS): Signals are synchronous by default and simpler for state management. Observables are better for event streams; signals are better for state.
`javascript
// Signals naturally handle diamond dependency problems
const firstName = new Signal.State('John');
const lastName = new Signal.State('Doe');
const fullName = new Signal.Computed(
() => \\${firstName.get()} \${lastName.get()}\
);
const greeting = new Signal.Computed(
() => \Welcome, \${fullName.get()}\
);
// Even if both firstName and lastName change, // greeting only recalculates once — no glitches Signal.subtle.untrack(() => { firstName.set('Jane'); lastName.set('Smith'); }); ``
The Signals proposal is one of the most exciting additions to JavaScript, promising to unify how we think about reactivity across the entire ecosystem.