Home >Web Front-end >JS Tutorial >Signals: Fine-grained Reactivity for JavaScript Frameworks
This article explores in depth how to use signals in Solid, a modern, responsive JavaScript library that mainly relies on components to build user interfaces.
Content:
Key points:
Signal introduction
One of the latest trends in web development is the use of signals, which provides a more responsive way to update easily variable values in your program. When a value is updated, all content using that value is updated as well. That's why the signal is so unique.
The growth of the signal and its attention are reminiscent of all the sensation caused when React 16.8 was released in 2019, when the React team introduced the hook. The goal of the hook is to make state updates (eventually all updates) more functional methods and stay away from using classes. While the signals look almost the same as the hook, there are some slight differences between them (we explore it below).
What is Solid?
Solid (also known as SolidJS) was founded in 2016 by Ryan Carniato and released in 2018. In his own words, “It stems from the desire to continue using the fine-grained reactive pattern I love from Knockout.js.”
He didn't like the direction of development of libraries such as React and Vue, "he just prefers the control and composability brought by using smaller and more independent primitives than components." His solution was to create Solid, This is a responsive framework that uses signals to create fine-grained responsiveness (a signal pattern that can now be seen in many other frameworks as well).
At first glance, Solid is very similar to React with hooks and functional components. In some ways, this is true: they all have the same philosophy when it comes to managing data, which makes learning Solid easier if we are already familiar with React.
However, there are some key differences:
What exactly is the signal?
Signal is based on observer mode, which is one of the classic Gang of Four design patterns. In fact, Knockout uses something very similar to a signal, called an "observable object".
Signals are the most atomic part of a responsive application. They are observable values, have initial values, and provide getter and setter methods for viewing or updating this value, respectively. However, to make the most of the signal, we need reactions, which are the effects of subscribing to the signal and running in response to changes in value.
When the value of the signal changes, it actually emits an event (or "signal") and then triggers a reaction (or "effect"). This is usually a call to update and render any component that depends on this value. These components are called subscribe signals. This means that if the value of the signal changes, only these components will be updated.
A key concept in Solid is that Everything is an effect, even a view rendering. Each signal is closely linked to the specific components it affects. This means that when the value changes, the view can be rerendered in a very fine-grained way without expensive full-page rerendering.
Signal example
To create a signal in Solid, we need to use the createSignal function and assign two variables to its return value as shown below:
<code class="language-javascript">const [name, setName] = createSignal("Diana Prince");</code>
These two variables represent the getter and setter methods. In the example above, name is getter and setName is setter. The 0 value passed to createSignal indicates the initial value of the signal.
This certainly looks familiar to React developers. Using React hook to create a similar code looks like this:
<code class="language-javascript">const [name, setName] = useState("Diana Prince");</code>
In React, getter(name) behaves like a variable, while setter(setName) is a function.
However, even if they look very similar, the main difference is that name behaves like a variable in React, while in Solid it is a function.
Taking name as a function means that when called inside the effect, it automatically subscribes the effect to the signal. This means that when the value of the signal changes, the effect will run with the new value.
The following is an example using the name() signal:
<code class="language-javascript">const [name, setName] = createSignal("Diana Prince");</code>The
createEffect function can be used to run effects based on any signal value, such as recording the value to the console. It will subscribe to any signal referenced inside the function. If the value of any signal changes, the effect code will run again.
In our example, if we use the setName setter function to change the value of the name signal, we can see the effect code run and record the new name to the console:
<code class="language-javascript">const [name, setName] = useState("Diana Prince");</code>
Taking getter as a function also means always returning the latest current value, while other frameworks may return "stale" values even after the value is updated. It also means that any signal can be easily bound to the calculated value and memorized:
<code class="language-javascript">createEffect(() => console.log(`Hello ${name()}`))</code>
This will create a read-only signal that can be accessed using nameLength(). Its value will be updated in response to any change in the name signal value.
If the name() signal is included in the component, the component will automatically subscribe to this signal and re-render when its value changes:
<code class="language-javascript">setName("Wonder Woman")</code>
Updating the value of the name signal with setName will cause HelloComponent to re-render. Additionally, the HelloComponent function is called only once to create the relevant HTML. Once it is called, it does not need to run again, even if there is any update to the value of the name signal. However, in React, component functions are called whenever any changes occur in the values they contain.
Another major difference to Solid is that although it uses JSX for view logic, it does not use virtual DOM at all. Instead, it precompiles the code using modern Vite build tools. This means that there is less JavaScript required to be delivered and no actual Solid library is required to be delivered with it (very similar to Svelte). Views are built in HTML. Then, use the template text system to recognize any changes and then perform good old-style DOM operations for a fine-grained update dynamically.
These isolation and fine-grained updates to specific areas of the DOM are very different from React's approach to completely rebuilding a virtual DOM after any changes. Direct updates to the DOM reduce the overhead of maintaining the virtual DOM and make it extremely fast. In fact, Solid has some impressive data in terms of rendering speeds—second only to native JavaScript.
All benchmarks can be viewed here.
Signal in Angular
As mentioned earlier, Angular recently adopted signals for fine-grained updates. They work similarly to the signals in Solid, but are created slightly differently.
To create a signal, you can use the signal function and pass the initial value as a parameter:
<code class="language-javascript">const nameLength = createMemo(() => name().length)</code>
The variable name assigned to the signal (name in the example above) can then be used as a getter:
<code class="language-javascript">const [name, setName] = createSignal("Diana Prince");</code>
The signal also has a set method that can be used to update its value as shown below:
<code class="language-javascript">const [name, setName] = useState("Diana Prince");</code>
The fine-grained update method in Angular is almost the same as in Solid. First, Angular has an update() method that works similar to set, but derives values instead of substituting values:
<code class="language-javascript">createEffect(() => console.log(`Hello ${name()}`))</code>
The only difference here is to take the value (name) as a parameter and execute the instruction on it (.toUpperCase()). This is useful when the final value of the getter being replaced is unknown and must be derived.
Secondly, Angular also has a computed() function for creating memorized signals. It works exactly the same as Solid's createMemo:
<code class="language-javascript">setName("Wonder Woman")</code>
Like Solid, the value of the computed signal changes whenever a change in the value of the signal in the calculation function is detected.
Finally, Angular has the effect() function, which works exactly the same as the createEffect() function in Solid. Whenever the value it depends on is updated, side effects are re-executed:
<code class="language-javascript">const nameLength = createMemo(() => name().length)</code>
Other features of Solid
It is not only signals that make Solid worthy of attention. As we have already mentioned, it is very fast in creating and updating content. It also has a very similar API to React, so it should be easy to get started for anyone who has used React before. However, Solid works very differently at the underlying layer and generally performs better.
Another nice feature of Solid is that it adds some clever features to JSX, such as control flow. It allows us to create a for loop using the
In addition, the
For anyone who wants to try Solid, there is an excellent introductory tutorial on the Solid website where we can experiment with the code in Solid playground.
Conclusion
In this article, we introduce the concept of signals and how they are used in Solid and Angular. We also looked at how they help Solid perform fine-grained updates to DOM without the need for virtual DOM. Many frameworks now adopt signal paradigms, so they are definitely the tricks we should master!
FAQ on Signals in JavaScript
Traditional event processing in JavaScript involves attaching an event listener to a DOM element and responding to user interactions or system events. However, this approach can become complex and difficult to manage in large applications. On the other hand, signals provide a finer granular approach to responsiveness. They allow you to create independent reactive behavior units that can be combined to create complex interactions. Unlike event listeners, signals are not bound to any specific DOM element or event type, which makes them more flexible and easier to reuse in different parts of the application.
Signals are designed to be efficient and performant. They use pull-based reactive models, meaning they only calculate their values when needed. This can lead to significant performance improvements in applications with complex reactive behaviors, as unnecessary computations can be avoided. However, the exact performance differences will depend on the specific use case and the degree of optimization of the reactive model.
Yes, signals can be used with other JavaScript frameworks such as React or Vue. They can be integrated into the reactive systems of these frameworks to provide additional flexibility and control over the behavior of responses. However, it is important to note that each framework has its own way of handling reactiveness, so you need to understand how signals fit into the model.
Debugging JavaScript applications that use signals may be slightly different from debugging traditional event-driven applications. Since the signal is not bound to a specific event or DOM element, you need to track the flow of data in the signal and its dependencies. Tools like Chrome DevTools are helpful for this, as they allow you to step through the code and check the status of the signal at each step.
While signals provide many benefits, they also have some limitations. One potential drawback is that they can make your code more complex and difficult to understand, especially for developers who are not familiar with the concept of reactiveness. Furthermore, since signals are relatively new concepts in JavaScript, there may be less resource and community support available than more mature patterns and frameworks.
The JavaScript application using signals can be tested using the same techniques as any other JavaScript application. You can use unit tests to test individual signals and their behavior, and use integration tests to test how signals interact with each other and with other parts of your application. Tools like Jest or Mocha can be used for this purpose.
Yes, the signal can be used in the Node.js environment. They are not bound to the browser or DOM, so they can be used in any JavaScript environment. This makes them a powerful tool for building reactive behavior in server-side applications.
The method of handling errors in JavaScript applications using signals is similar to any other JavaScript application. You can use the try/catch block to catch errors and handle them properly. Additionally, you can use error signals to propagate errors through the signal graph and process them in a centralized manner.
Yes, signals can be used to build real-time applications. They provide a powerful way to manage and respond to real-time data updates, making them an excellent choice for applications such as chat applications, real-time dashboards, and multiplayer games.
Optimizing the performance of JavaScript applications that use signals involves careful management of signal dependencies and avoiding unnecessary calculations. You can use tools like Chrome DevTools to analyze your application and identify performance bottlenecks. Additionally, you can use techniques such as memory and delay evaluation to improve the performance of your signal.
The above is the detailed content of Signals: Fine-grained Reactivity for JavaScript Frameworks. For more information, please follow other related articles on the PHP Chinese website!