Signals in JavaScript: The Native Reactivity Standard Coming Soon
Hello HaWkers, one of the most exciting proposals for the future of JavaScript is gaining momentum. Signals, a reactivity pattern already adopted by frameworks like Angular, Vue, Solid, and Svelte, may soon become a native part of the JavaScript language.
Are you ready for a change that could unify how all frameworks handle reactive state?
What Are Signals
Signals are reactivity primitives that allow creating observable values that automatically notify dependents when they change. Unlike solutions like Redux or Context API, Signals are granular and efficient by design.
Basic Concept
// Current native Signals proposal
// (syntax may change until final approval)
// Creating a signal
const count = new Signal.State(0);
// Reading the value
console.log(count.get()); // 0
// Updating the value
count.set(1);
// Computed values that react automatically
const doubled = new Signal.Computed(() => count.get() * 2);
console.log(doubled.get()); // 2
// When count changes, doubled updates automatically
count.set(5);
console.log(doubled.get()); // 10Important context: The proposal is being developed by TC39 (the committee that defines ECMAScript) with contributions from engineers at Angular, Vue, Solid, and other frameworks.
Why Signals Are Important
Reactivity is fundamental for modern interfaces, but each framework implements its own solution. This causes fragmentation and makes interoperability difficult.
The Current Problem
Each framework has its approach:
- React: Virtual DOM + reconciliation
- Vue: Proxies + own reactivity system
- Angular: Zone.js + signals (recent)
- Solid: Signals + compilation
- Svelte: Compilation + build-time reactivity
Consequences:
- Code not portable between frameworks
- Libraries need wrappers for each framework
- Developers learn different systems
- Performance varies greatly between implementations
The Solution: Native Signals
// With native signals, libraries can be agnostic
// A state library that works anywhere
class Counter {
#count = new Signal.State(0);
get count() {
return this.#count.get();
}
increment() {
this.#count.set(this.#count.get() + 1);
}
decrement() {
this.#count.set(this.#count.get() - 1);
}
}
// Works with React, Vue, Angular, Solid, Svelte...
const counter = new Counter();
How Signals Work
Signals are based on a dependency graph that automatically tracks which values depend on which.
Basic Architecture
// Signal.State: values that can be read and written
const firstName = new Signal.State("John");
const lastName = new Signal.State("Doe");
// Signal.Computed: derived values (read-only)
const fullName = new Signal.Computed(() => {
// Dependencies are tracked automatically
return `${firstName.get()} ${lastName.get()}`;
});
console.log(fullName.get()); // "John Doe"
// Only firstName is updated
firstName.set("Jane");
// fullName recalculates automatically only what's needed
console.log(fullName.get()); // "Jane Doe"Effects and Reactions
// Signal.subtle.Watcher: observe changes
const watcher = new Signal.subtle.Watcher(() => {
console.log("Some monitored signal changed!");
});
// Add signals to monitor
watcher.watch(firstName);
watcher.watch(lastName);
// When any changes, the callback is called
firstName.set("Alice"); // Log: "Some monitored signal changed!"Automatic Batching
// Multiple updates are grouped efficiently
const items = new Signal.State([]);
const filter = new Signal.State("");
const filteredItems = new Signal.Computed(() => {
const f = filter.get();
return items.get().filter(item => item.includes(f));
});
// Even with multiple updates...
items.set(["apple", "banana", "cherry"]);
filter.set("a");
// ...the computed only recalculates once
console.log(filteredItems.get()); // ["apple", "banana"]
Comparison With Current Solutions
See how native Signals compare to current approaches.
React useState vs Signals
// React: useState
function Counter() {
const [count, setCount] = useState(0);
const doubled = count * 2; // Recalculates on every render
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
// With Signals (conceptual)
function Counter() {
const count = useSignal(0);
const doubled = useComputed(() => count.value * 2);
return (
<div>
<p>Count: {count.value}</p>
<p>Doubled: {doubled.value}</p>
<button onClick={() => count.value++}>+</button>
</div>
);
}Vue Reactivity vs Signals
// Vue: ref and computed
import { ref, computed } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
// Native signals (similar, but standardized)
const count = new Signal.State(0);
const doubled = new Signal.Computed(() => count.get() * 2);
Benefits For Developers
Adopting native Signals will bring several benefits.
Granular Performance
// Signals update only what needs updating
const todos = new Signal.State([
{ id: 1, text: "Learn Signals", done: false },
{ id: 2, text: "Build app", done: false },
]);
const completedCount = new Signal.Computed(() => {
return todos.get().filter(t => t.done).length;
});
const pendingCount = new Signal.Computed(() => {
return todos.get().filter(t => !t.done).length;
});
// If only one item's 'done' changes,
// only relevant computeds recalculateSimpler Code
// Without signals: manual state management
class Store {
constructor() {
this.state = { count: 0 };
this.listeners = [];
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
increment() {
this.state.count++;
this.listeners.forEach(l => l(this.state));
}
}
// With signals: automatic reactivity
class Store {
count = new Signal.State(0);
increment() {
this.count.set(this.count.get() + 1);
}
}Current Proposal Status
The Signals proposal is in active development at TC39.
Expected Timeline
2024:
- Stage 1: Initial proposal accepted
- Discussions about API and semantics
2025:
- Stage 2: Draft specification
- Experimental implementations in engines
- Polyfills available
2026-2027 (estimate):
- Stage 3: Implementation candidate
- Browsers start implementations
2027+ (estimate):
- Stage 4: Final approval
- Available in all modern browsers
How to Prepare
You can start understanding Signals today.
Learn Solid.js
// Solid has the API closest to the proposal
import { createSignal, createEffect } from 'solid-js';
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log("Count changed:", count());
});
setCount(1); // Log: "Count changed: 1"Explore Angular Signals
// Angular 16+ uses signals natively
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const doubled = computed(() => count() * 2);
effect(() => {
console.log(`Count: ${count()}, Doubled: ${doubled()}`);
});
count.set(5); // Log: "Count: 5, Doubled: 10"Conclusion
Native Signals in JavaScript represent a fundamental change in how we build reactive applications. By standardizing reactivity in the language, we gain interoperability between frameworks, consistent performance, and simpler code.
Although the proposal is still in development, the concepts are already available in frameworks like Solid and Angular. Learning about Signals today is investing in the future of web development.
If you want to understand more about trends shaping modern JavaScript, I recommend checking out our article on VoidZero and Vite Plus where we discuss the new generation of JavaScript tools.

