JavaScript Signals and Fine-Grained Reactivity

This article explores JavaScript signals, a state management primitive gaining widespread adoption across modern front-end frameworks. You will learn what signals are, how their underlying dependency tracking mechanisms enable fine-grained reactivity, how they differ from traditional component-level rendering models, and why they are being considered for standardization in the JavaScript language itself.

What Are JavaScript Signals?

Signals are data structures that store a value and automatically notify consumers when that value changes. Unlike plain JavaScript variables, signals act as reactive wrappers around state. They combine a getter to read the value and track dependencies with a setter to update the value and notify subscribers.

A standard signal implementation consists of three primary primitives:

How Fine-Grained Reactivity Works

Fine-grained reactivity is an execution model where only the exact operations dependent on a changed piece of state are executed, rather than re-evaluating entire components or subtrees.

Signals achieve this through dynamic, automatic dependency tracking:

  1. Subscription During Read: When a computed signal or an effect executes, it sets itself as the “active consumer.” Reading a signal’s value inside that scope registers the consumer as a subscriber to that specific signal.
  2. Dependency Graph Construction: The runtime automatically builds a dynamic directed acyclic graph (DAG) of state nodes and dependent nodes without requiring manual dependency arrays.
  3. Targeted Notification: When a signal’s value changes via its setter, it walks the dependency graph and notifies only the direct subscribers.
  4. Surgical DOM Updates: In frameworks that leverage signals (such as SolidJS, Preact, and Qwik), UI bindings subscribe directly to signals. When a signal updates, it alters the specific DOM node directly, bypassing the need to re-run entire component render functions or reconcile a Virtual DOM.

Signals vs. Component-Level Reactivity

Traditional frameworks often rely on component-level re-rendering. When state changes in a component, the entire component function re-executes, generating a new Virtual DOM tree that must be diffed against the previous tree to calculate DOM patches.

Signals shift reactivity from the component boundary to the expression boundary:

The TC39 Signals Proposal

Due to the convergence of frameworks like Solid, Preact, Angular, Vue, and Svelte on signal-based primitives, a formal proposal has been introduced to the TC39 committee to standardize signals within the ECMAScript specification.

A standardized Signal API aims to provide a common low-level foundation, enabling interoperability between different web frameworks, libraries, and vanilla JavaScript applications while optimizing performance at the engine level.