search
HomeWeb Front-endJS TutorialWTF Is Reactivity !?
WTF Is Reactivity !?Dec 22, 2024 am 05:44 AM

Reactivity Models Explained

Foreword

It’s been (already) 10 years since I started developing applications and websites, but the JavaScript ecosystem has never been more exciting than it is today!

In 2022, the community was captivated by the concept of "Signal" to the point where most JavaScript frameworks integrated them into their own engine. I’m thinking about Preact, which has offered reactive variables decoupled from the component lifecycle since September 2022; or more recently Angular, which implemented Signals experimentally in May 2023, then officially starting from version 18. Other JavaScript libraries have also chosen to rethink their approach...

Between 2023 and until now, I’ve consistently used Signals across various projects. Their simplicity of implementation and usage has fully convinced me, to the extent that I’ve shared their benefits with my professional network during technical workshops, training sessions, and conferences.

But more recently, I started asking myself if this concept was truly "revolutionary" / if there are alternatives to Signals? So, I delved deeper into this reflection and discovered different approaches to reactive systems.

This post is an overview of different reactivity models, along with my understanding of how they work.

NB: At this point, you’ve probably guessed it, I won’t be discussing about Java’s "Reactive Streams"; otherwise, I’d have titled this post "WTF Is Backpressure!?" ?

Theory

When we talk about reactivity models, we're (first and foremost) talking about "reactive programming", but especially about "reactivity".

The reactive programming is a development paradigm that allows to automatically propagate the change of a data source to consumers.

So, we can define the reactivity as the ability to update dependencies in real time, depending on the change of data.

NB: In short, when a user fills and/or submits a form, we must react to these changes, display a loading component, or anything else that specifies that something is happening... Another example, when receiving data asynchronously, we must react by displaying all or part of this data, executing a new action, etc.

In this context, reactive libraries provide variables that automatically update and propagate efficiently, making it easier to write simple and optimized code.

To be efficient, these systems must re-compute/re-evaluate these variables if, and only if, their values ​​have changed! In the same way, to ensure that the broadcasted data remains consistent and up-to-date, the system must avoid displaying any intermediate state (especially during the computation of state changes).

NB: The state refers to the data/values used throughout the lifetime of a program/application.

Alright, but then… What exactly are these "reactivity models"?

PUSH, a.k.a "Eager" Reactivity

The first reactivity model is called "PUSH" (or "eager" reactivity). This system is based on the following principles:

  • Initialization of data sources (as known as "Observables")
  • Components/Functions subscribe to these data sources (these are the consumers)
  • When a value changes, data is immediately propagated to the consumers (as known as "Observers")

As you might have guessed, the "PUSH" model relies on the "Observable/Observer" design pattern.

1st Use Case : Initial State and State Change

Let’s consider the following initial state,

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;

WTF Is Reactivity !?

Using a reactive library (such as RxJS), this initial state would look more like this:

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));

NB: For the sake of this post, all code snippets should be considered as "pseudo-code."

Now, let’s assume that a consumer (a component, for example) wants to log the value of state D whenever this data source is updated,

d.subscribe((value) => console.log(value));

Our component would subscribe to the data stream; it still needs to trigger a change,

a.next({ firstName: "Jane", lastName: "Doe" });

From there, the "PUSH" system detects the change and automatically broadcasts it to the consumers. Based on the initial state above, here’s a description of the operations that might occur:

  • State change occurs in data source A!
  • The value of A is propagated to B (computation of data source B);
  • Then, the value of B is propagated to D (computation of data source D);
  • The value of A is propagated to C (computation of data source C);
  • Finally, the value of C is propagated to D (re-computation of data source D);

WTF Is Reactivity !?

One of the challenges of this system lies in the order of computation. Indeed, based on our use case, you’ll notice that D might be evaluated twice: a first time with the value of C in its previous state; and a second time with the value of C up to date! In this kind of reactivity model, this challenge is called the "Diamond Problem" ♦️.

2nd Use Case : Next Iteration

Now, let’s assume the state relies on two main data sources,

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;

When updating E, the system will re-compute the entire state, which allows it to preserve a single source of truth by overwriting the previous state.

  • State change occurs in data source E!
  • The value of A is propagated to B (computation of data source B);
  • Then, the value of B is propagated to D (computation of data source D);
  • The value of A is propagated to C (computation of data source C);
  • The value of E is propagated to C (re-computation of data source C);.
  • Finally, the value of C is propagated to D (re-computation of data source D);

WTF Is Reactivity !?

Once again, the "Diamond Problem" occurs... This time on the data source C which is potentially evaluated 2 times, and always on D.

Diamond Problem

The "Diamond Problem" isn't a new challenge in the "eager" reactivity model. Some computation algorithms (especially those used by MobX) can tag the "nodes of the reactive dependency tree" to level out state computation. With this approach, the system would first evaluate the "root" data sources (A and E in our example), then B and C, and finally D. Changing the order of state computations helps to fix this kind of problem.

WTF Is Reactivity !?

PULL, a.k.a "Lazy" Reactivity

The second reactivity model is called "PULL". Unlike the "PUSH" model, it is based on the following principles:

  • Declaration of reactive variables
  • The system defers state computation
  • Derived state is computed based on its dependencies
  • The system avoids excessive updates

It’s this last rule that is most important to remember: unlike the previous system, this last one defers state computation to avoid multiple evaluations of the same data source.

1st Use Case : Initial State and State Change

Let's keep the previous initial state...

WTF Is Reactivity !?

In this kind of system, the initial state syntax would be in the following form:

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));

NB: React enthusiasts will likely recognize this syntax ?

Declaring a reactive variable gives "birth" to a tuple: immutable variable on one side; update function of this variable on the other. The remaining statements (B, C and D in our case) are considered as derived states since they "listen" to their respective dependencies.

d.subscribe((value) => console.log(value));

The defining characteristic of a "lazy" system is that it doesn't propagate changes immediately, but only when explicitly requested.

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;

In a "PULL" model, using an effect() (from a component) to log the value of a reactive variable (specified as a dependency) triggers the computation of the state change:

  • D will check if its dependencies (B and C) have been updated;
  • B will check if its dependency (A) has been updated;
  • A will propagate its value to B (computing the value of B);
  • C will check if its dependency (A) has been updated;
  • A will propagate its value to C (computing the value of C)
  • B and C will propagate their respective value to D (computing the value of D);

WTF Is Reactivity !?

An optimization of this system is possible when querying dependencies. Indeed, in the scenario above, A is queried twice to determine whether it has been updated. However, the first query could be enough to define if the state has changed. C wouldn't need to perform this action... Instead, A could only broadcast its value.

2nd Use Case : Next Iteration

Let's complicate the state somewhat by adding a second reactive variable "root",

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));

One more time, the system defers state computation until it is explicitly requested. Using the same effect as before, updating a new reactive variable will trigger the following steps:

  • D will check if its dependencies (B and C) have been updated ;
  • B will check if its dependency (A) has been updated ;
  • C will check if its dependencies (A and E) have been updated ;
  • E will propagate its value to C, and C will fetch the value of A via memoization (computing the value of C) ;
  • C will propagate its value to D, and D will fetch the value of B via memoization (computing the value of D) ;

WTF Is Reactivity !?

Since the value of A hasn't changed, recomputing this variable is unnecessary (same thing applies to the value of B). In such cases, the use of memoization algorithms enhances performance during state computation.

PUSH-PULL, a.k.a "Fine-Grained" Reactivity

The last reactivity model is the "PUSH-PULL" system. The term "PUSH" reflects the immediate propagation of change notifications, while "PULL" refers to fetching the state values on demand. This approach is closely related to what is called "fine-grained" reactivity, which adheres to the following principles:

  • Declaration of reactive variables (we're talking about reactive primitives)
  • Dependencies are tracked at an atomic level
  • Change propagation is highly targeted

Note that this kind of reactivity isn't exclusive to the "PUSH-PULL" model. Fine-grained reactivity refers to the precise tracking of system dependencies. So, there are PUSH and PULL reactivity models which also work in this way (I'm thinking about Jotai or Recoil.

1st Use Case : Initial State and State Change

Still based on the previous initial state... The declaration of an initial state in a "fine-grained" reactivity system would look like this:

let a = { firstName: "John", lastName: "Doe" };
const b = a.firstName;
const c = a.lastName;
const d = `${b} ${c}`;

NB: The use of the signal keyword isn't just anecdotal here ?

In terms of syntax, it’s very similar to the "PUSH" model, but there is one notable and important difference: dependencies! In a "fine-grained" reactivity system, it’s not necessary to explicitly declare the dependencies required to compute a derived state, as these states implicitly track the variables they use. In our case, B and C will automatically track changes to the value of A, and D will track changes to both B and C.

let a = observable.of({ firstName: "John", lastName: "Doe" });
const b = a.pipe(map((a) => a.firstName));
const c = a.pipe(map((a) => a.lastName));
const d = merge(b, c).pipe(reduce((b, c) => `${b} ${c}`));

In such a system, updating a reactive variable is more efficient than in a basic "PUSH" model because the change is automatically propagated to the derived variables that depend on it (only as a notification, not the value itself).

d.subscribe((value) => console.log(value));

Then, on demand (let's take the logger example), the use of D within the system will fetch the values ​​of the associated root states (in our case A), compute the values ​​of the derived states (B and C), and finally evaluate D. Isn't it an intuitive mode of operation?

WTF Is Reactivity !?

2nd Use Case : Next Iteration

Let's consider the following state,

a.next({ firstName: "Jane", lastName: "Doe" });

Once again, the "fine-grained" aspect of the PUSH-PULL system allows for automatic tracking of each state. So, the derived state C now tracks root states A and E. Updating the variable E will trigger the following actions:

  • State change of the reactive primitive E!
  • Targeted change notification (E to D via C);
  • E will propagate its value to C, and C will retrieve the value of A via memoization (computing the value of C);
  • C will propagate its value to D, and D will retrieve the value of B via memoization (computing the value of D);

WTF Is Reactivity !?

This is that prior association of reactive dependencies with each other that makes this model so efficient!

Indeed, in a classic "PULL" system (such as React's Virtual DOM, for example), when updating a reactive state from a component, the framework will be notified of the change (triggering a "diffing" phase). Then, on demand (and deferred), the framework will compute the changes by traversing the reactive dependency tree; every time a variable is updated! This "discovery" of the state of dependencies has a significant cost...

With a "fine-grained" reactivity system (like Signals), the update of reactive variables/primitives automatically notifies any derived state linked to them of the change. Therefore, there’s no need to (re)discover the associated dependencies; the state propagation is targeted!

Conclusion(.value)

In 2024, most web frameworks have chosen to rethink how they work, particularly in terms of their reactivity model. This shift has made them generally more efficient and competitive. Others choose to be (still) hybrid (I'm thinking about Vue here), which makes them more flexible in many situations.

Finally, whatever the model chosen, in my opinion, a (good) reactive system is built upon a few main rules:

  1. The system prevents inconsistent derived states;
  2. The use of a state within the system results in a reactive derived state;
  3. The system minimizes excessive work ;
  4. And, "for a given initial state, no matter the path the state follows, the system's final result will always be the same!"

This last point, which can be interpreted as a fundamental principle of declarative programming, is how I see a (good) reactive system as needing to be deterministic! This is that "determinism" that makes a reactive model reliable, predictable, and easy to use in technical projects at scale, regardless of the complexity of the algorithm.

The above is the detailed content of WTF Is Reactivity !?. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Replace String Characters in JavaScriptReplace String Characters in JavaScriptMar 11, 2025 am 12:07 AM

Detailed explanation of JavaScript string replacement method and FAQ This article will explore two ways to replace string characters in JavaScript: internal JavaScript code and internal HTML for web pages. Replace string inside JavaScript code The most direct way is to use the replace() method: str = str.replace("find","replace"); This method replaces only the first match. To replace all matches, use a regular expression and add the global flag g: str = str.replace(/fi

How do I create and publish my own JavaScript libraries?How do I create and publish my own JavaScript libraries?Mar 18, 2025 pm 03:12 PM

Article discusses creating, publishing, and maintaining JavaScript libraries, focusing on planning, development, testing, documentation, and promotion strategies.

How do I optimize JavaScript code for performance in the browser?How do I optimize JavaScript code for performance in the browser?Mar 18, 2025 pm 03:14 PM

The article discusses strategies for optimizing JavaScript performance in browsers, focusing on reducing execution time and minimizing impact on page load speed.

jQuery Matrix EffectsjQuery Matrix EffectsMar 10, 2025 am 12:52 AM

Bring matrix movie effects to your page! This is a cool jQuery plugin based on the famous movie "The Matrix". The plugin simulates the classic green character effects in the movie, and just select a picture and the plugin will convert it into a matrix-style picture filled with numeric characters. Come and try it, it's very interesting! How it works The plugin loads the image onto the canvas and reads the pixel and color values: data = ctx.getImageData(x, y, settings.grainSize, settings.grainSize).data The plugin cleverly reads the rectangular area of ​​the picture and uses jQuery to calculate the average color of each area. Then, use

How do I debug JavaScript code effectively using browser developer tools?How do I debug JavaScript code effectively using browser developer tools?Mar 18, 2025 pm 03:16 PM

The article discusses effective JavaScript debugging using browser developer tools, focusing on setting breakpoints, using the console, and analyzing performance.

How to Build a Simple jQuery SliderHow to Build a Simple jQuery SliderMar 11, 2025 am 12:19 AM

This article will guide you to create a simple picture carousel using the jQuery library. We will use the bxSlider library, which is built on jQuery and provides many configuration options to set up the carousel. Nowadays, picture carousel has become a must-have feature on the website - one picture is better than a thousand words! After deciding to use the picture carousel, the next question is how to create it. First, you need to collect high-quality, high-resolution pictures. Next, you need to create a picture carousel using HTML and some JavaScript code. There are many libraries on the web that can help you create carousels in different ways. We will use the open source bxSlider library. The bxSlider library supports responsive design, so the carousel built with this library can be adapted to any

Enhancing Structural Markup with JavaScriptEnhancing Structural Markup with JavaScriptMar 10, 2025 am 12:18 AM

Key Points Enhanced structured tagging with JavaScript can significantly improve the accessibility and maintainability of web page content while reducing file size. JavaScript can be effectively used to dynamically add functionality to HTML elements, such as using the cite attribute to automatically insert reference links into block references. Integrating JavaScript with structured tags allows you to create dynamic user interfaces, such as tab panels that do not require page refresh. It is crucial to ensure that JavaScript enhancements do not hinder the basic functionality of web pages; even if JavaScript is disabled, the page should remain functional. Advanced JavaScript technology can be used (

How to Upload and Download CSV Files With AngularHow to Upload and Download CSV Files With AngularMar 10, 2025 am 01:01 AM

Data sets are extremely essential in building API models and various business processes. This is why importing and exporting CSV is an often-needed functionality.In this tutorial, you will learn how to download and import a CSV file within an Angular

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
3 weeks agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

DVWA

DVWA

Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

EditPlus Chinese cracked version

EditPlus Chinese cracked version

Small size, syntax highlighting, does not support code prompt function

SublimeText3 Linux new version

SublimeText3 Linux new version

SublimeText3 Linux latest version