The JavaScript language recently added the Signals proposal (currently in Stage 1) to the list of candidate features striving to improve the language. The Signals proposal seeks to provide common primitives primarily for framework maintainers to implement reactive programming patterns. The proposal reflects input from authors/maintainers of Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz, and more.
Reactive applications essentially require: an interface to external systems to receive input events and send actions; computing the reaction to the input event; and sending the corresponding actions to the matching external systems (e.g., screen display, remote databases). With functional UI approaches (e.g., Elm), the reaction computation relies on a pure function (called the reactive function) such that (actions_n, state_n+1) = f(state_n, event_n)
, where:
n
is the nth event processed by the reactive system,state_n
is the state of the reactive system when the nth event is processed.
Many frameworks for implementing user interfaces (Angular2, Vue, React,
etc.) rather make use of callback procedures, or event handlers, which, as a result of an event, directly perform the corresponding reaction. Deciding which actions to perform (be it input validation, local state update, error handling, or data fetching) often means accessing and updating some pieces of state that are not always in scope. Frameworks thus include some state management, dependency injection, or communication capabilities to handle delivering state where it is needed and updating it when allowed and required.
An alternative that has gained popularity in recent years is, when convenient and possible, to declare the relationship between input events and pieces of state (e.g., button click -> increment °C), between pieces of state themselves (e.g., °F = °C * 9/5 + 32
), and between pieces of state and reactions (e.g., °C changes -> update gauge color on the screen). Those declarations happen once and for all, eliminating a range of bugs where developers update a variable’s dependency and forget to update the variable itself.
Some UI frameworks thus have developers declare these relationships using ad-hoc primitives and syntax ($
in Svelte; ref
, reactive
, and computed
in Vue). Beyond differing syntax, such framework may adopt differing ways of implementing reactivity, and possibly slightly differing semantics. The proposal admittedly targets framework maintainers and the interoperability of their approaches:
Differently from Promises/A+, we’re not trying to solve for a common developer-facing surface API, but rather the precise core semantics of the underlying signal graph. [,] The signal API here is a better fit for frameworks to build on top of, providing interoperability through a common signal graph and auto-tracking mechanism.
The plan for this proposal is to do significant early prototyping, including integration into several frameworks, before advancing beyond Stage 1. We are only interested in standardizing Signals if they are suitable for use in practice in multiple frameworks, and provide real benefits over framework-provided signals.
The proposal provides a simple example of a counter-implementation:
const counter = new Signal.State(0);
const isEven = new Signal.Computed(() => (counter.get() & 1) == 0);
const parity = new Signal.Computed(() => isEven.get() ? "even" : "odd");
// A library or framework defines effects based on other Signal primitives
declare function effect(cb: () => void): (() => void);
effect(() => element.innerText = parity.get());
// Simulate external updates to counter...
setInterval(() => counter.set(counter.get() + 1), 1000);
The example showcases the syntax for declaring independent pieces of state (Signal.state
), pieces of state tied to their dependencies (Signal.computed
), and how a library maintainer can leverage the signal primitives to link the execution of actions to state changes (effect(...)
).
The proposal includes an implementation that features automatic dependency tracking, lazy evaluation, and memoization. Automatic dependency tracking provides better developer ergonomics (vs. manually providing dependencies —cf. React’s useMemo). Lazy evaluation and memoization prevent unnecessary and untimely computations, improving the performance profile of the API.
Interesting discussions occurred on Reddit, with one developer reflecting:
There is maybe a https://xkcd.com/927/ situation going on here, sure. But it’s pretty significant that I think all of the big frameworks are involved in creating the standard. So, this is going from a whole bunch of ways of solving the problem that signals solve and having just one instead (with frameworks building on that one for their specific needs).
[…] Being in browsers means it’ll be potentially more performant and memory efficient, even if only slightly (slight improvements can make a significant difference at this scale).There are basically two fundamental takes on what should and shouldn’t be included in ECMAScript. [One camp] thinks that only the essentials should be added/included and devs should reinvent their own wheel (or use some JS library). The other camp thinks something more along the lines of JS providing APIs for common problems and welcoming standards like this, and
Object.groupBy()
over lodash… fewer dependencies, less code to ship, less frustration with “well, how does this library solve the problem vs the one I’m familiar with?”
Interested readers are invited to read the full proposal online. The GitHub repository contains plenty of explanations and code samples that serve to clarify the goal, syntax, and semantics of the proposal.
Reactive programming facilitates the development of event-driven, reactive applications by providing abstractions to express time-varying values and automatically managing dependencies between such values. A number of approaches have been proposed across various languages, such as Haskell, Scheme, JavaScript, Java, .NET, and more. Reactive programming is particularly relevant for JavaScript — one of the native browser languages used for web applications.