How to understand the iterative motivation and design philosophy of Fiber architecture?

Before we understand the Fiber architecture, here’s how the React team positioned React in the React Philosophy:

  • We believe React is the preferred way to build large, responsive Web applications in JavaScript. It does well on Facebook and Instagram.

The four words in this paragraph are “fast response,” which is arguably the React team’s most important user experience priority. This can be seen in the React 15 era: Due to its obsession with “fast response”, React managed to optimize the time complexity of the ORIGINAL O(N3) Diff to an unprecedented O(n).

However, with the passage of time and the increasing complexity of the business, the React Stack Reconciler, which was once celebrated with great relish, gradually becomes a bit tired in terms of experience. To further implement the principle of “rapid response,” the React team rewrote its core Diff algorithm in the 16.x edition, putting it in a new face: “Fiber Reconciler.”

So what are the deeply rooted limitations of the Stack Reconciler that forced the React to make architectural changes? What is the Fiber architecture, and how different is the reconciliation process based on it? So let’s talk about these two big questions.

1. Pre-knowledge: Single-threaded JavaScript versus multi-threaded browsers

When you get started with the front end, you’ve probably heard the conclusion that JavaScript is single-threaded and browsers are multi-threaded.

For multi-threaded browsers, in addition to dealing with JavaScript threads, they also need to deal with various task threads including event systems, timers/delayers, network requests and so on, including, of course, the UI rendering thread responsible for processing DOM. JavaScript threads can manipulate the DOM.

What does that mean? If the renderer thread and the JavaScript thread are working at the same time, the rendering result is very unpredictable: for example, the renderer thread can just draw a picture, and a piece of JavaScript will change it completely. This dictates that the JavaScript thread and the render thread must be mutually exclusive: the two threads cannot be interspersed and must be serial. While one thread is executing, the other thread can only suspend and wait.

A similar feature is Event threads, where the browser’s event-loop mechanism determines that Event tasks are maintained by an asynchronous queue. When the event is triggered, the corresponding task will not be executed immediately, but the event thread adds it to the end of the task queue, waiting for the JavaScript synchronization code to complete, and then execute the queue in the idle time.

Under such a mechanism, if the JavaScript thread is tied to the main thread for a long time, the render level update will have to wait for a long time, and the interface will not be updated for a long time, resulting in a user experience called “stutter”. What do you do when your page gets stuck? You might click and click on the page more often, expecting the page to respond a little bit. Unfortunately, the event thread is also waiting for JavaScript, which makes the event that is fired difficult to respond to.

Imagine an interface that doesn’t update, an interaction that doesn’t respond. Isn’t that frustrating? That is the dilemma facing Stack Reconciler in its later years.

2. Why is there such a dilemma as “stuck”?

One of the unsolved problems posed by Stack Reconciler is the time-out of JavaScript on the main thread. Why is this a problem? This brings to mind a key point highlighted in the previous article that Stack Reconciler is a synchronous, recursive process.

The synchronous recursive process means that there is no turning back, meaning that once the update starts, it’s like eating dazzle, and you can’t stop. To review this process, take a look at the following figure:

React 15 and its previous versions use the virtual DOM tree as a data structure carrier in computer science. The traversal idea of the Diff algorithm also follows the “comparison between two trees” algorithm in traditional computer science and is optimized on this basis. Therefore, in essence, Diff algorithm based on stack harmonic mechanism is actually a depth-first traversal process of tree. Depth-first tree traversal is always related to recursion.

Take this tree as an example. If component A is updated, stack reconciliation works like this: compare the two As at level 1, confirm that the nodes are reusable, and continue to Diff the sub-components. When Diff reaches B, the two B nodes before and after are compared and found reusable, so the sub-nodes D and E continue to Diff. After the Diff at the deepest level of the B tree is completed and layer by layer backtracking, the Diff logic of the C node is entered…… The harmonizer repeats the process of “parent calling child” until the deepest layer of nodes is updated, and then slowly back up.

The lethality of this process is that it is synchronous and cannot be interrupted. When dealing with relatively complex and large virtual DOM trees, the Stack Reconciler takes a long time to reconcile, which means that JavaScript threads occupy the main thread for long periods of time, leading to the stalling/gridlock of rendering and long periods of unresponsive interaction described above.

3. Design Idea: How does Fiber solve the problem

What is Fiber? Literally, the word Fiber translates as “silk, Fiber.” It’s a thing thinner than thread. In computer science, there are processes and threads, and Fiber is a process that is even thinner than a thread. The advent of fibers is intended to provide finer control over the rendering process.

Fiber is a polysemy. From an architectural perspective, Fiber is a rewrite of the React core algorithm (the harmonic process). From the perspective of encoding, Fiber is a data structure defined internally by React. It is the node unit of the Fiber tree structure, which is also the “virtual DOM” under the React 16 architecture. From a workflow point of view, Fiber nodes hold the state and side effects of components that need to be updated, and a Fiber also corresponds to a unit of work.

The next step is to understand Fiber from an architectural perspective.

The purpose of the Fiber architecture, according to React officials, is to enable “incremental rendering.” Incremental rendering, in layman’s terms, is the splitting of a rendering task into multiple rendering tasks and then spreading them over multiple frames. However, strictly speaking, incremental rendering is actually just a means to achieve the purpose of incremental rendering, in order to achieve “interruptible” and “recoverable” tasks, and give different tasks different “priorities”, and ultimately achieve a smoother user experience.

4. Fiber architecture core: “interruptible” “Recoverable” and “Priority”

Prior to React 16, React’s rendering and update phases relied on a two-tier architecture as shown below:

As analyzed above, the Reconciler layer is responsible for comparing the changes between the old and new virtual DOM, and the Renderer layer is responsible for applying the changes to the view, and the process from Reconciler to Renderer is “strictly synchronized.”

In React 16, in order to achieve “interruptibility” and “priority,” the two-tier architecture was changed to a three-tier architecture as shown below:

The additional layer of architecture is called a “Scheduler,” which schedules the priority of updates.

In this architectural pattern, the update processing workflow looks like this: First, each update task is assigned a priority. When the update tasks reach the Reconciler, the high-priority update tasks (referred to as A) are more quickly scheduled into the Reconciler layer; When A new, updated task, called B, arrives at the Reconciler, the scheduler checks its priority, and if IT finds that B has A higher priority than the current Task, task A, which is currently in the Reconciler’s layer, is interrupted, and the scheduler pushes task B into the Reconciler’s layer. When task B completes the Reconciler’s rendering, A new round of scheduling begins, and the previously interrupted Task A is pushed back into the Reconciler’s layer to continue its rendering journey, which is called “recoverable”.

That’s how architecture deals with the three core concepts of “interruptible”, “recoverable” and “priority”.

5. The impact of Fiber architecture on the lifecycle

As discussed earlier, the React 16 lifecycle is divided into three phases, as shown in the following figure:

  • Render phase: Pure and without side effects, may be suspended, terminated, or restarted by React.

  • Pre-commit phase: DOM can be read.

  • Commit phase: You can use DOM, run side effects, and schedule updates.

Both pre-commit and COMMIT belong to the COMMIT stage.

In the Render phase, React mainly performs calculations in memory to determine the update point of the DOM tree. The COMMIT phase, on the other hand, is responsible for actually executing the updates generated by the RENDER phase. What these two phases do is well understood in conjunction with the React architecture layering described above.

React 15 render to commit

In React 16, render to commit looks like this:

As can be seen, the impact of the old and new architectures on the React lifecycle is mainly in the render phase, which is achieved by adding Scheduler layers and rewriting Reconciler layers.

In the Render phase, a large update task is broken down into work units with different priorities. React interrupts and recovers work units based on their priorities. Since the render phase is actually “invisible” to the user, even if it is interrupted and restarted, it is zero perception to the user. However, the restart of the unit of work (that is, the task) will be accompanied by repeated execution of part of the lifecycle:

  • componentWillMount

  • componentWillUpdate

  • shouldComponentUpdate

  • componentWillReceiveProps

ShouldComponentUpdate is a function that returns true or false to help determine the need for an update.

And the three life cycles at the beginning of “componentWill” are abused by developers in various postures all year long, which is the “heavy disaster area” of side effects. On this note, in the previous article “Why React 16 changes the component lifecycle? There has been a very detailed explanation in “(ii)”, which will not be repeated here. What needs to be done here is to establish a connection between the changes in the React architecture layers and the changes in the life cycle, so as to form a deeper understanding of the design motivations of both.

6, summary

In this chapter, we learned about the architecture layers and workflow from a macro perspective of the Fiber architecture in React 16. But all this is only a starting point for learning about Fiber Reconciler. Fiber Reconciler is, for the moment, still a black box, and there are so many mysteries to be explored, including, but not limited to:

  • Does React 16 render asynchronously in all cases?

  • How does “interruptible” and “recoverable” in Fiber architecture actually work?

  • How are Fiber trees different from traditional virtual DOM trees?

  • How is priority scheduling implemented?

.

The answer to all of these questions can be found in the Fiber source code for React.

Next, I’ll use the reactdom.render link as a starting point to explore the source code related to Fiber.

What exactly happened after reactdom.render? What happens after this.setState? Once you have an idea of these two problems, all the little problems listed above will be solved.

Learning the source (the article reprinted from) : kaiwu.lagou.com/course/cour…