Mixed Signals
How Reactivity evolved in the JavaScript Ecosystem
Why?
I was always curious about how things worked. Then I became a programmer, and things didn’t change, quite the opposite, it all became very confusing and overwhelming, especially because I did not know how things worked behind the scene. Using libraries and frameworks like Django and Angular gave me the impression that there was a lot of magic happening, without my knowledge. So, in the last couple of years, while I became a little bit better at this trade, I started to ask myself complex questions, like how do frameworks actually work, how are JSX and interpolated templates converted to HTML, how do reactive values work, and what the hell is a Virtual DOM?
In a way, through these questions I got to write this article and have a presentation about it, trying to understand how reactivity became a necessity and how it evolved in the JS Ecosystem, through all of its good and bad phases, and the direction that it’s heading towards.
A bit of history
Looking back in time, at how JavaScript evolved as a language, and the emergence of all the libraries and frameworks that rose to power, the ones that slowly died, and those who knew how to reinvent themselves, we can outline some patterns that result into today’s emergence of Signals.
In the early 2000’s everything was server-side. Compiled or dynamic languages ruled with their web frameworks, such as CakePHP and Symphony for PHP, ASP.NET for C#, Servlet and Spring for Java, Rails for Ruby or Django for Python. And in these early days, JavaScript was not viewed as it is today. It was considered more of a toy language, used for small, self contained widgets. And there were no modules, no NPM, so everything was global.
Then we got XHR, Ajax, and Google proved it’s worth with Google Web Toolkit, allowing for libraries such as Dojo, Mootools and jQuery to come to life, simplifying DOM interactions and extending the language capabilities. In 2007 / 2008 we get the smartphones, and the need for server - client separation appears. And then, the big bang happens.
Knockout, Backbone and Angular emerge almost at the same time, with React and Vue following a few years later. They were libraries, selling themselves as “view-layers” instead of fully fledged frameworks. Instead of solving all the problems needed for a frontend app, they would focus on just solving rendering problems. And it was good, because you could incrementally migrate your server-side monolith, one page or component at a time.
We witness a lot of revolutions in the community, with new frameworks emerging almost every year, and existing ones bringing improvements and major updates. Everybody gets a full-stack framework support, and you really got a pletora of choices: Angular, Next, Nuxt, SvelteKit, Remix, Astro, Qwik, and the list can go on.
But in 2020, SolidJS brings something “new” to the table, Signals. And slowly but surely, everybody starts to fall in love with them. Vue calls them ref
s, Angular, Preact and Qwik implemented them, and Svelte practices this magic by casting $runes
. Basically, everybody realized that Knockout was right, 14 years ago.
Sooo… what are Signals?
A signal is a reactive primitive that is used for managing application state. Based, at it’s core, on the Observer Pattern, it provides a simplified API, that is based on a set of reactive principles (features) that allow for excellent developer ergonomics and a optimized implementation when rendering.
API
The API would consist of three parts:
- Root State
- Derived State (also known as computed)
- Effects
The root state is what holds the value (think of it as a bucket). Derived state is based on root state, and changes accordingly, when the state that it depends on also changes.
And effects represent code that should run as a response to a state change.
Features
- Dependency Tracking. The ability to track which signals depend on each other. It has to be optimal, meaning that there is a dependency graph that represents all of the dependencies, and it should happen automatically, so that the developer doesn’t have to worry about it.
- Lazy by default. If something is not used, it should not be tracked. If a tree falls in the forest … you know the rest. Also, derived state is not evaluated on declaration, only when requested.
- Memoization. Or one of the ugliest words I know. Simply put, cache the last value. If a dependency is not changed, you know its worth.
- Push-then-pull. This is the core of signals, in my opinion.
If we think of the Observer Pattern, then Subjects would be Push based. They notify every time their observers of changes. Pull based would mean that the ones that depend on a signal, should pull their value when it changes.
Push-then-pull (at least how I understand it) is that the Signal sends a notification when it changes, marking itself as “dirty”. All dependents are notified and they are responsible to update themselves, pulling the new value. In the case of multiple dependencies that change at the same time, the dependent will retrieve them first, with a graph pass, and only afterwards the rendering will happen.
With all of these, Signals have the following Core Features:
- Fine Grained Reactivity
- Glitch-free Rendering
TC39 Proposal
There is a proposal in the works, to bring Signals as a standard to the JavaScript language. Still in Stage 1, so it still has a long way until it reaches us or the runtime, but this proposal is very interesting, since it wants to provide an API for handling reactive state at the language core. This proposal is made for frameworks to build on top of, providing interoperability through common signal graph and auto-tracking mechanism, rather than providing a common developer-facing surface API.
🚧 [section under construction] 🚧
Resources
- 🚦 JavaScript Signals standard proposal🚦 (TC39)
- The Evolution of Signals in JavaScript (by Ryan Carniato)
- A Hands-On Introduction to Fine-Grained Reactivity (by Ryan Carniato)
- A Brief History of Reactivity (by Miško Hevery)
- What is Reactivity? (by pzuraq) - I recommend the whole series (3 articles)
- Third Age of JavaScript (by Swyx)
- Four Eras of JavaScript Frameworks (by pzuraq)
- Introducing Signals (Preact announcement)