The introduction

When React first came into view, the concept of Virtual DOM (VDOM) was quite impressive. Before operating on the real DOM, the Virtual DOM (VDOM) was compared to the parts that needed to be updated before operating on the real DOM. This reduced the cost of multiple DOM operations by the browser. The process, officially known as Reconciliation, translates as reconciliation algorithm. However, with the development of React today, the Reconciliation becomes tired with the growing number of front-end applications, and react Fiber comes out. React Fiber is a rewrite of the React core algorithm that took the React team over two years to complete.

motivation

VDOM was applauded by everyone at that time, why today will be slightly tired, this also starts from its working principle. When React was released, it was envisaged that UI rendering would be asynchronous in the future, as can be seen from the design of setState() and the transaction mechanism inside React. In previous versions of react@16, Reconciler (now called Stack Reconciler) used top-down recursions to update the entire subtree, starting with the root component or the component after setState(). This is fine if the tree is small, but as the tree grows larger, recursive traversal becomes more expensive and continues to occupy the main thread, so that the layout, animation and other periodic tasks and interaction responses on the main thread cannot be handled immediately, resulting in a visual lag.

Theoretically, the maximum number of frames that the human eye can recognize is no more than 30, and the number of frames of a movie is mostly fixed at 24. The optimal frame rate of a browser is 60, that is, about 16.5ms per rendering. The normal workflow of the browser should look like this: Operation -> Render -> Operation -> Render…

But when JS takes too long to execute, this is what happens, and the FPS (the number of frames per second) drops causing a visual lag.

So how to address this issue is what Fiber Reconciler does. In a nutshell, you can look at the following figure and split the JS to make sure it doesn’t block the Main thread.

The working principle of

Splitting a synchronization task is understandable, but before splitting it, we face the following problems:

  • What hurt?
  • How to hurt?
  • What is the order of execution after the split?

What hurt

# React@15DOM Actual DOM node ------- Instances VDOM tree node maintained by React ------- ElementstypeThe props)Copy the code

In react@15, the update is performed in two steps: 1. Diff Diff compares the prevInstance and nextInstance status to find the difference and the corresponding VDOM change. Diff is essentially a set of calculations (traversal, comparison) that are separable (half of them will be done later). 2. Patch updates the difference queue calculated by diFF algorithm to the real DOM node. React does not calculate a difference and perform patch once. Instead, it calculates all the differences and puts them into the difference queue, and then executes patch once to complete the actual DOM update.

The final patch phase is a series of DOM operations. Although it can be split according to the change list obtained after diff, it has little significance. It will not only cause the internal DOM state maintained to be inconsistent with the actual one, but also affect the experience. So what you should do is split up the diff phase. Below is the process of ReactDOM rendering 10,000 child components. As you can see, the main thread is occupied during the diff execution phase and cannot perform any other operation I/O operations until the run is complete.

How to open

Thus, the React Fiber solution is introduced. The Fiber tree is constructed based on the VDOM tree. The tree structure is exactly the same, but the information contained is different. The following is part of the structure of the Fiber Tree node:

{alternate: Fiber | null, / / in the Fiber update cloned image of Fiber, the Fiber will modify the tag on the Fiber nextEffect: Fiber | null, / / singly linked list structure, convenient traversal Tree have side effects on Fiber node pendingWorkPriority: PriorityLevel, / / tag subtree to update task priority stateNode: Any, // Manage instance's own featuresreturnPoint: Fiber | null, / / Fiber child parent node in the Tree: Fiber | null, / / points to the first child node (: Fiber | null, / /} to the nodeCopy the code

Fiber processes ReactElement through return, child and Sibling in sequence, transforming the previously simple tree structure into a single-linked list based tree structure, maintaining more node relationships.

Execution order

A Stack is executed in a tree unit. Fiber executes in a Fiber unit. The Stack can only execute synchronously; Fiber can be scheduled for this Fiber. In other words, if you have A Fiber with A Linked List of A → B → C, if A is interrupted from B, B → C can be executed again later, which is difficult for the Stack’s synchronous processing structure.

React Fiber is implemented in two phases:

  1. render / reconciliation (interruptible)
  2. commit (not interruptible)

In the first stage, the main work is to build a complete Fiber Tree from top to bottom. In rerender, the Fiber Tree named workInProgress is built according to the previously generated Tree for update operation.

Suppose I have a DOM structure like the one shown above that NEEDS to be rendered. The first time I render it will generate the Fiber Tree shown below:

Since I needed to square the value in Item, I clicked Button and react started to build the workInProgress Tree based on the previously generated Fiber Tree. During the construction process, compare each fiber node from top to bottom. If the root node is not changed, copy the List node into the WorkinProgress Tree according to its child pointer. After a Fiber node is processed, React checks whether the current time slice is sufficient. If the current time slice is insufficient, react marks the priority of the task to be processed and determines the next time slice based on the priority.

RequestIdleCallback allows a low-priority task to be called during the idle period, while requestAnimationFrame allows a high-priority task to be called on the next stack frame, ensuring that the main thread executes the fiber cell according to priority. The priorities are in the following order: Text box input > Tasks to be completed at the end of this scheduling > Animation Transition > Interactive feedback > Data update > Tasks that will not be displayed in case they will be displayed in the future.

module.exports = {  
  // heigh level
  NoWork: 0, // No work is pending.
  SynchronousPriority: 1, // For controlled text inputs. 
  TaskPriority: 2, // Completes at the end of the current tick.
  AnimationPriority: 3, // Needs to complete before the next frame.
  
  // low level
  HighPriority: 4, // Interaction that needs to complete pretty soon to feel responsive.
  LowPriority: 5, // Data fetching, or result from updating stores.
  OffscreenPriority: 6, // Won't be visible but do the work in case it becomes visible.
};
Copy the code

In the process of square calculation, React compares the fiber nodes to find changes in List, Item2, and Item3, and then puts a Tag in the corresponding generated workInProgress Tree and pushes it to the Effect List.

When reconciliation is over, all side effects including DOM change are recorded in the root node’s Effect list, and the update operation is performed in the second phase (COMMIT) to complete the process.

In this example, the detailed comparison process is not explained in detail. It is recommended to watch Lin Clark’s talk in last year’s React Conf, which is very easy to understand and the examples in this article also come from this talk.

Imagining the future

  • At this year’s JS Conf in Iceland, Dan mentioned the concept of asynchronous rendering. Asynchronous rendering is not about loading components one by one, but about loading in an asynchronous manner while giving the experience of a synchronous process. On older devices, by sacrificing some load time to get a smooth experience. In React@16, asynchronous rendering is turned off by default. Although it is possible to hack asynchronous rendering, there are still bugs because there are no tests written.
  • In react@16, the previous life cycle function is still supported, but it has been officially stated that it will be scrapped in the next version, mainly because of the reconciliation rewrite. In the render/reconciliation process, due to the concepts of priority and time slices, a task may be replaced halfway through by another task with a higher priority, or it may be terminated due to time reasons. When the task is executed again, from scratch, it will result in some componentwillLife cycles can be invoked multiple times and affect performance. The React team gave us a long time to deal with this issue, and officials provided a lot of referencescaseTo smooth the transition to the next version.

react@16 is more of a transition than a watershed, and a lot of the work is to inoculate users and tell you what to do next, so react@17 will be the one to make waves. The reconciliation rewrite opens up a lot of possibilities for the future of React, including Hooks that are currently being discussed in the community. Fiber is also a possibility. In the subsequent version, I think there will be a small change in the writing method, mainly for better performance services; In addition, some community-generated solutions should be optimized to make writing more user-friendly (refs and context transmission in HOC), and official solutions to common problems should be provided (asynchronous data processing), etc. In addition to its advantages, it also brings some problems. The learning curve for newcomers to React is likely to get steeper as the number of concepts in react increases over time.

conclusion

React doesn’t do well when dealing with large applications. The main reason is that the calculation takes too long. As a result, the main thread is always occupied and cannot process other tasks. To solve this problem, the React team proposed Fiber reconciliation instead of Stack reconciliation. Compared with Stack, Fiber adopts the asynchronous method to split the previously synchronous calculation process, so that the host thread will not be occupied all the time, and can have time to deal with other tasks, such as I/O operations, interactive feedback, etc.

reference

  • React Fiber Architecture – zhihu
  • React Fiber – Nuggets
  • Illustrate the basic working principle of the browser – Zhihu
  • The React 16 Fiber source quick reference | Zindex ‘s blog
  • React Fiber status check – CYB – Medium
  • To fully understand the React Fiber | dark feather blown
  • A Cartoon Intro to Fiber
  • Blog /from-jsx-to-dom.md at Master · Xieyu /blog · GitHub
  • Sneak Peek: Beyond React 16