Let’s talk about the future of front-end framework Signals

Signals is far ahead in the current selection of front-end frameworks!

In the last week before the National Day, I was Code Reviewing the React code of a new classmate and found that he wanted to use memo and useCallback to render only the modified sub-component part. In fact, this feature is difficult to achieve in React. Because after the React state changes, the render function will be re-executed. That is, after calling setState in the component, the entire function will be executed again.

React itself can't do that. But the framework based on Signals does not do this. Through automatic state binding and dependency tracking, it only re-executes the code block that uses the state after the current state changes.

I personally didn't explain this issue too much at the time, but just briefly explained the rendering mechanism of React. Here is a summary of Signals.

Advantage

Compared with React, the framework state response granularity based on Signals is very fine. Here takes Solid as an example:

import { createSignal, onCleanup } from "solid-js";

const CountingComponent = () => {
  // 创建一个 signal
  const [count, setCount] = createSignal(0);

  // 创建一个 signal
  const [count2] = createSignal(666);

  // 每一秒递增 1
  const interval = setInterval(() => {
    setCount((c) => c + 1);
  }, 1000);

  // 组件销毁时清除定时器
  onCleanup(() => clearInterval(interval));

  return (
    <div>
      <div>
        count: {count()}
        {console.log("count is", count())}
      </div>
      <div>
        count2: {count2()}
        {console.log("count2 is", count2())}
      </div>
    </div>
  );
};

The above code will only print count when count changes alone, but will not print count2 data at all.

The console print looks like this:

  • count is 0
  • count2 is 666
  • count is 1
  • count is 2
  • ...

Judging from the printing results, Solid will only execute the rendering function once at the beginning, and will only render the changed DOM nodes subsequently. This is impossible to do in React. React is based on view-driven. State changes will re-execute the entire rendering function, and React is completely unable to recognize how the state is used. Developers can even implement React through the following code of re-rendering.

const [, forceRender] = useReducer((s) => s + 1, 0);

In addition to fine-grained updates, the framework mental model using Signals is also simpler. The biggest feature is that developers don't have to worry about where the state is defined, nor where the corresponding state is rendered. As follows:

import { createSignal } from "solid-js";

// 把状态从过组件中提取出来
const [count, setCount] = createSignal(0);
const [count2] = createSignal(666);

setInterval(() => {
  setCount((c) => c + 1);
}, 1000);

// 子组件依然可以使用 count 函数
const SubCountingComponent = () => {
  return <div>{count()}</div>;
};

const CountingComponent = () => {
  return (
    <div>
      <div>
        count: {count()}
        {console.log("count is", count())}
      </div>
      <div>
        count2: {count2()}
        {console.log("count2 is", count2())}
      </div>
      <SubCountingComponent />
    </div>
  );
};

The above code can still run normally. Because it is state-driven. When developers use Signal within a component, it is local state, and when they define Signal outside the component, it is global state.

Signals on their own are not that valuable, but combined with derived state and side effects they are. The code looks like this:

import {
  createSignal,
  onCleanup,
  createMemo,
  createEffect,
  onMount,
} from "solid-js";

const [count, setCount] = createSignal(0);

setInterval(() => {
  setCount((c) => c + 1);
}, 1000);

// 计算缓存
const doubleCount = createMemo(() => count() * 2);

// 基于当前缓存
const quadrupleCount = createMemo(() => doubleCount() * 2);

// 副作用
createEffect(() => {
  // 在 count 变化时重新执行 fetch
  fetch(`/api/${count()}`);
});

const CountingComponent = () => {
  // 挂载组件时执行
  onMount(() => {
    console.log("start");
  });

  // 销毁组件时执行
  onCleanup(() => {
    console.log("end");
  });

  return (
    <div>
      <div>Count value is {count()}</div>
      <div>doubleCount value is {doubleCount()}</div>
      <div>quadrupleCount value is {quadrupleCount()}</div>
    </div>
  );
};

As you can see from the above code, neither derived state nor side effects require filling in dependencies like React, and side effects are also separated from the life cycle (the code is easier to read).

Implementation Mechanism

Fine-grained, high-performance, and without any limitations. It is worthy of being hailed as the future of front-end frameworks. So how exactly is it achieved?

Essentially, Signals are a value container that tracks dependencies when accessed and triggers side effects when changed.

This paradigm based on reactive underlying types is not a particularly new concept in the front-end world: it dates back more than a decade to implementations like Knockout observables and Meteor Tracker. Vue's optional API follows the same principle, except that the basic type is hidden behind object properties. Relying on this paradigm, Vue2 has very good performance without any need for optimization.

dependency collection

React useState returns the current state and a set value function, while Solid's createSignal returns two functions. Right now:

type useState = (initial: any) => [state, setter];

type createSignal = (initial: any) => [getter, setter];

Why does createSignal need to pass the getter method instead of directly passing the corresponding state value? This is because in order for the framework to be responsive, Signal must collect who is interested in its value. Merely passing status cannot provide any information about Signal. The getter method not only returns the corresponding value, but also creates a subscription during execution to collect all dependency information.

Template compilation

To ensure the high performance of the Signals framework, this function must be implemented in conjunction with template compilation. Framework developers achieve dynamic and static separation through template compilation, and with dependency collection, point-to-point DOM updates can be achieved when state variables change. Therefore, the current mainstream Signals framework does not use virtual DOM. Vue, which is based on virtual DOM, currently relies on the compiler to achieve similar optimizations.

Let's first take a look at Solid template compilation:

const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  const interval = setInterval(() => {
    setCount((c) => c + 1);
  }, 1000);

  onCleanup(() => clearInterval(interval));
  return <div>Count value is {count()}</div>;
};

Corresponds to the compiled component code.

const _tmpl$ = /*#__PURE__*/ _$template(`<div>Count value is `);

const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  const interval = setInterval(() => {
    setCount((c) => c + 1);
  }, 1000);

  onCleanup(() => clearInterval(interval));
  return (() => {
    const _el$ = _tmpl$(),
      _el$2 = _el$.firstChild;
    _$insert(_el$, count, null);
    return _el$;
  })();
};
  • Execute the _tmpl$ function to obtain the static template of the corresponding component
  • Extract the count function in the component and bind the state function to the corresponding template position through _$insert
  • When calling the setCount function to update, compare the corresponding count, and then modify the corresponding _el$ corresponding data

other

You can take a look at the mainstream frameworks that use Signals:

However, it seems that the React team may not use Signals at present.

  • Signals perform well, but are not a good way to write UI code
  • Plans to improve performance through compiler
  • Primitives like Signals may be added

PREACT The author wrote @preact/signals-react to provide Signals for React. However, I personally do not recommend using it in a production environment.

The space is limited, I will interpret the source code of @preact/signals-core later .

References

encourage

If you think this article is good, I hope you can give me some encouragement and help star it on my github blog.

blog address

The author of the open source framework NanUI switched to selling steel, and the project was suspended. The first free list in the Apple App Store is the pornographic software TypeScript. It has just become popular, why do the big guys start to abandon it? TIOBE October list: Java has the biggest decline, C# is approaching Java Rust 1.73.0 Released A man was encouraged by his AI girlfriend to assassinate the Queen of England and was sentenced to nine years in prison Qt 6.6 officially released Reuters: RISC-V technology becomes the key to the Sino-US technology war New battlefield RISC-V: Not controlled by any single company or country, Lenovo plans to launch Android PC
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/wsafight/blog/10115779