Applications of Signals in JavaScript

20088ef2d986ba87af51378af5c6e1b7.jpeg

Recently, “Signals” has become a topic of much concern on the front end. Many foreign bigwigs have posted that Signals is the future of the front-end framework. At the same time, You Da also added the “Connection to Signals” section on the Vue official website. In addition, multiple front-end frameworks including Solid, Angular, Preact, Qwik, and Vue have begun to implement Signals.

As a FE, if you don’t know much about Signals like I did before, then this article may help you better understand this technology. This article will introduce the history, concept and advantages of Signals.

1. History

The Signals mechanism has been around since the dawn of declarative JavaScript frameworks. It has taken on many different names over time, coming in and out over the years.

In a declarative JavaScript framework, a component is a unit that declares its output, which can be rendered and composed dynamically. The advantage of this approach is that it allows developers to focus on the output of the component without worrying about how the component is rendered and updated. This abstraction also makes components easier to reuse, and easier to understand and test.

However, this abstraction also poses some challenges. One of the challenges is how components communicate and share state. These issues can lead to code that becomes unwieldy, difficult to maintain, and prone to confusion in complex applications. Therefore, developers need a more flexible and powerful communication mechanism to solve these problems.

In this case, the Signals mechanism becomes a useful solution. The Signals mechanism allows components to communicate without directly referencing other components, and enables more flexible delivery of messages and states. These mechanisms can be events, callbacks, promises, or other asynchronous mechanisms. They can be used to handle various scenarios such as user interaction, network requests and state changes, etc.

The Signals mechanism has many other advantages as well. They improve the maintainability and scalability of applications, and they help developers better understand and debug code. Also, since the Signals mechanism allows for loose coupling between components, they also help increase code reusability.

1.1 early implementation

It is sometimes surprising that multiple teams arrive at similar solutions around the same time. The beginnings of a declarative JavaScript framework have three versions: Knockout.js (July 2010), Backbone.js (October 2010) and Angular.js (October 2010).

Angular’s dirty checking, Backbone’s model-driven re-rendering, and Knockout’s fine-grained updates. Each one is slightly different, but both will ultimately form the basis of how we manage state and update the DOM today.

1.2 Data binding

A common pattern in Angular.js is called data binding. Data binding is a way to attach parts of state to a specific part of the view tree. A powerful thing that can be done is to make it bidirectional. Thus, we can have the state update the DOM, and in turn, DOM events automatically update the state, all in a simple declarative way. But it can also be problematic if abused, in Angular, if it doesn’t know what has changed, it does a dirty check of the whole tree, propagating up can cause it to happen multiple times.

1.3 Mobx

After that is the era of react, react does not have too many restrictions on state management. MobX is such a solution. It emphasizes consistency and hassle-free communication. That is, each part of the system is run exactly once for any given change, and synchronously in the proper order.

It does this by exchanging the typical push-based reactivity of previous schemes for a push-pull hybrid system. Notifications of changes are pushed, but execution of the derived state is deferred until it is read.

dc21176c80499f3b810da8b628084bbf.png

image

1.4 Vue

Vue (2014) also contributed greatly to today’s development. In addition to being consistent with MobX in terms of optimizing consistency, Vue has “fine-grained” responsiveness at its core from the beginning.

While Vue shares the use of the virtual DOM with React, the responsiveness is top-notch, meaning it was first developed as an internal mechanism alongside the framework to support its Options API and, over the past few years, the core of its Composition API (2020).

Vue takes the pull/push mechanism a step further by scheduling tasks. By default, Vue’s modification will not be executed immediately, but will not be executed until the next microtask.

However, this scheduling can also be used to do some other things, such as keep-alive, and Suspense. Even things like concurrent rendering can be achieved this way, really showing how to get the best of both pull and push based approaches.

2. Why Signals

Signals are unique in that state changes automatically update components and UI in the most efficient way possible. Signals offers great ergonomics based on automatic state binding and dependency tracking, with a unique implementation optimized for virtual DOM.

2.1 The dilemma of state management

As applications become more complex, there will be more and more components in the project, and more and more states that need to be managed.

In order to achieve component state sharing, it is generally necessary to promote the state to the common ancestor component of the component and pass it down through props. The problem is that when updating, all subcomponents will be updated accordingly. It is necessary to cooperate with memo and useMemo to optimize performance.

Although this sounds reasonable, as the project code increases, it is difficult for us to determine where these optimizations should be placed.

Even with memoization added, it is often invalidated by unstable dependency values, and since Hooks do not have an explicit dependency tree that can be analyzed, there is no way to use tools to find the cause.

793ef48c0191f7d3845b70eb57c7777d.png

image

Another solution is to put it on the Context, and the subcomponents, as consumers, get the required state through useContext.

But there is a problem. Only the value passed to the Provider can be updated, and it can only be updated as a whole, and fine-grained updates cannot be achieved.

In order to deal with this problem, the Context can only be split, and the business logic will inevitably depend on multiple Contexts, so the phenomenon of Context nesting dolls will appear.

5e676de9fb18a06d8bbf951d89529583.png

image

2.2 Signals to the future

The core of Signal is an object that holds values through the .value property. It has an important characteristic that the value of the Signal object can change, but the Signal itself always remains the same.

import { signal } from "@preact/signals";

const count = signal(0);

// Read a signal's value by accessing .value:
console.log(count.value); // 0

// Update a signal's value:
count.value += 1;

// The signal's value has changed:
console.log(count.value); // 1

In Preact, when a Signal is passed down as a prop or context, what is passed is a reference to the Signal. This allows the Signal to be updated without re-rendering the component, since the Signal object is passed to the component instead of its value.

This allows us to skip all the expensive rendering work and immediately jump to any component that accesses the signal.value property.

b33c114a90f89460950703f3630aabbf.png

image

Signals have a second important characteristic that they keep track of when their values are accessed and when they are updated. In Preact, accessing Signal’s properties from within a component automatically re-renders the component when the Signal’s value changes.

Through the use of Preact, we can summarize the characteristics of Signals: 1. It feels like using the original data structure 2. It can be automatically updated according to the change of the value 3. Directly update the DOM (in other words, no VDOM) 4. No dependencies array

3. Use in SolidJS

const Greeting = (props) => (
  <>Hi <span>{props.name}</span></>
);

const App(() => {
  const [visible, setVisible] = createSignal(false),
    [name, setName] = createSignal("Josephine");

  return (
    <div onClick={() => setName("Geraldine")}>{
      visible() & amp; & amp; <Greeting name={ name } />
    }</div>
  );
});

render(App, document. body);

You can see that SolidJS responsiveness is also based on Signal. createSignal can be used both inside and outside the component, which is similar to Preact. On the one hand, Signal can be used as the local state of the component, or it can be defined as the global State. Similar to the previous, there are also the following similarities in SolidJS:

  • Responsive fine-grained updates

  • No need to define dependencies

  • lazy value

SolidJS is very similar to Mobx and Vue’s responsiveness, but instead of dealing with VDOM, it updates the DOM directly.

4. Manually implement a

Three elements of responsive state management, Signals, Reactions, Derivations

// context contains execution methods and Signal dependencies in Reactions
const context = [];

const createSignal = (value) => {
  const subscriptions = new Set();
  const readFn = () => {
    const running = context. pop();
    if (running) {
      subscriptions. add({
        execute: running.execute
      });
      running.deps.add(subscriptions);
    }
    return value;
  };
  const writeFn = (newValue) => {
    value = newValue;
    for (const sub of [...subscriptions]) {
      sub. execute();
    }
  };
  return [readFn, writeFn];
};

const createEffect = (fn) => {
  const execute = () => {
    running. deps. clear();
    context. push(running);
    try {
      fn();
    } finally {
      context. pop(running);
    }
  };

  const running = {
    execute,
    deps: new Set()
  };
  execute();
};

const createMemo = (fn) => {
  const [memo, setMemo] = createSignal();
  createEffect(() => setMemo(fn()));
  return memo;
};

const [name, setName] = createSignal("a");
const fullName = createMemo(() => {
  return "c-" + name();
});
createEffect(() => console. log(name(), fullName()));
setName("b");

Signals is a basic data update and read, and Reactions can track changes subscribed to Signals, so set the value of Derivations in the Reactions function.

Five, finally

This article is some records of learning Signals. I hope that by introducing some history and concepts of responsive state management, you will have a comprehensive understanding of state management. If you feel that this article is not detailed enough, you can read the original text below.

6. Reference

  1. https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob

  2. https://dev.to/this-is-learning/react-vs-signals-10-years-later-3k71

  3. https://mp.weixin.qq.com/s/Tn0rbkCdFw4f-3ihKUEYQA

  4. https://indepth.dev/posts/1289/solidjs-reactivity-to-rendering

  5. https://preactjs.com/guide/v10/signals/

  6. https://preactjs.com/blog/introducing-signals

If you want to know more about Zhuanzhuan’s business practices, click to follow the official account below!