This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!

React Fiber is a major change and optimization that Facebook has been working on for more than two years. It is a reimplementation of the React core algorithm. It’s been more than three years since Facebook confirmed that React Fiber would be released in React 16 at the React Conf 2017 conference. Now that React 17 has been released, there’s been a lot of great community writing about Fiber.

This paper is based on a technical sharing within the team. By drawing on excellent articles from the community and integrating personal understanding, this paper starts from six questions to understand and understand React Fiber and briefly introduce and analyze the popular front-end framework Svelte. Hope to be helpful to those who are exploring React and various front-end frameworks.

The full text contains numerous references and references to the following blog posts, which readers can check out for themselves:

  • React Technology revealed
  • Front End Engineer self-cultivation: How does React Fiber make the update process manageable
  • Svelte, the emerging front-end framework, goes from inception to principle
  • React: Framework and performance (part 2)

I. What is the design concept of React?

The React philosophy section begins with:

We believe React is the preferred way to build large, responsive Web applications in JavaScript. It does well on Facebook and Instagram. One of the best parts of React is that it leads us to think about how to build an app.

Q: What are the constraints of fast response? Q: What are the constraints of fast response?

  • CPU bottlenecks: When a project becomes large, has too many components, runs into a computational-intensive operation, or has insufficient device performance that causes pages to drop frames and cause delays.
  • I/O bottleneck: After a network request is sent, it cannot respond quickly because it needs to wait for the data to be returned.

The Fiber architecture in this article is designed to address CPU and network issues, two of the most common issues that affect the front-end development experience. One causes lag and the other causes white screens. For this reason React introduces two new concepts to the front end: Time Slicing and Suspense.

2. React’s “congenital deficiency” — I heard that Vue 3.0 adopted Dom Diff, which combines dynamic and static. Why didn’t React follow up?

Dom Diff of Vue 3.0

Vue3.0 puts forward the DOM diff idea of dynamic and static combination. The DOM diff of dynamic and static combination is actually optimized in the pre-compilation stage. The precompile optimization is possible because Vue Core statically parses templates. The parse process uses regular expressions to parse templates sequentially, executing callbacks for opening tags, closing tags, and text. To construct the AST tree.

With the precompilation process, the precompilation optimization that Vue can do is very powerful. For example, by marking the component nodes in the template that might change at precompile time, you can skip the “never changing nodes” and only compare the “dynamic nodes that might change” when you do the pre-render diff again. This is the theoretical basis for DOM Diff with dynamic and static combination to optimize the positive correlation between diff cost and template size to positive correlation with dynamic nodes.

Can React be precompiled optimized like Vue?

Vue needs to do bidirectional data binding, data interception or proxy, so it needs to statically analyze the template in the pre-compilation phase, analyze what data the view depends on, and process it in a responsive manner. React is a local rerender. React gets, or handles, a bunch of recursive execution calls called React. CreateElement (see the code Babel converted below), which cannot be statically analyzed from the template level. JSX and the hand-written Render function are completely dynamic, with excessive flexibility resulting in insufficient information available for optimization at runtime.

JSX writing:

<div>
  <h1>Six questions to help you understand React Fiber</h1>
  <ul>
    <li>React</li>
    <li>Vue</li>
  </ul>
</div>
Copy the code

Recursive React. CreateElement method:

// After Babel conversion
React.createElement(
  "div".null,
 React.createElement(
    "h1".null."\u516D\u4E2A\u95EE\u9898\u52A9\u4F60\u7406\u89E3 React Fiber"
  ),
 React.createElement(
    "ul".null,
   React.createElement("li".null."React"),
   React.createElement("li".null."Vue")));Copy the code

JSX vs Template

  • JSX has the full expressiveness of JavaScript and can build very complex components. However, the flexible syntax also means that the engine is hard to understand and cannot anticipate the developer’s user intentions, making it difficult to optimize performance.
  • Template is a very constrained language. You can only write templates in a certain way.

Given these compile-time deficiencies, React has been working hard on run-time optimization. For example, React15 implements batchedUpdates (batch updates). That is, multiple setstates in the context of the same event callback only trigger one update.

However, if a single update is time-consuming, the page will still lag (which is common in large applications with long maintenance periods). This is because the update process of React15 is synchronized and cannot be interrupted until the page is rendered.

Data reference: to React, for instance, talk about framework and performance (bottom) | front-end framework Svelte emerging from the introduction to the principle

3. What optimizations have been made for the constantly attacking React from the perspective of architecture evolution?

React renders a page in two stages

  • Scheduling phase (Reconciliation) : In this phase, React updates data and generates a new Virtual DOM. Then, through the Diff algorithm, React quickly finds out the elements that need to be updated and puts them into the update queue to get the new update queue.
  • Commit: This is the phase when React traverses the update queue, updating all its changes to the DOM at once.

The React 15 architecture

The React15 architecture can be divided into two layers:

  • Reconciler — The component responsible for finding change;
  • Renderer – responsible for rendering the changed components to the page;

In React15 and before, the Reconciler creates the virtual DOM recursively, and the recursion process cannot be interrupted. If the component tree is very deep, the recursion can take up a lot of thread time, and the user interaction can get stuck if the recursion update takes more than 16ms.

To solve this problem, React16 reconstructs recursive, uninterruptible updates to asynchronous, interruptible updates because the virtual DOM data structures that were once used for recursion are no longer sufficient. The result is a new Fiber architecture.

The React 16 architecture

In order to solve the problem of page delays caused by synchronized updates occupying threads for a long time, and to explore more possibilities for runtime optimization, React began refactoring and continues to this day. The goal of refactoring is to implement Concurrent Mode.

From V15 to V16, the React team spent two years reconstructing the Stack Reconciler in the source code architecture to the Fiber Reconciler.

The React16 architecture can be divided into three layers:

  • Scheduler — The priority of scheduling tasks, and the high priority tasks enter the Reconciler first;
  • Reconciler — The component responsible for finding changes: the update effort goes from a recursive process to a cyclical process that can be broken. The Fiber architecture is adopted in the Reconciler.
  • Renderer – Responsible for rendering the changed components to the page.

The React 17 optimization

React16’s expirationTimes model can only distinguish if >=expirationTimes determines whether a node is updated. The Lanes model of React17 can select an update interval and dynamically increase or decrease the priority in the interval, which can process more fine-grained updates.

Lane uses binary bits to indicate the priority of tasks, which facilitates the calculation of priority (bit operation). Different priorities occupy different “tracks” in different locations, and there is the concept of batch. The lower the priority, the more “tracks”. High priorities interrupt low priorities, and what priority new tasks need to be assigned are all issues that Lane addresses.

The purpose of Concurrent Mode is to implement a interruptible/recoverable update mechanism. It consists of two parts:

  • A coroutine framework: the Fiber Reconciler
  • Heuristic updating algorithms based on coroutine architectures: algorithms that control how coroutine architectures work

Reference: React17 new feature: heuristic update algorithm

What does the browser do at a frame and the implications of requestIdleCallback

What does the browser do with one frame?

As we all know, the content of a page is drawn frame by frame, and the browser refresh rate represents how many frames a browser draws per second. In principle, the number of frames drawn within 1s is also more, the screen performance is also delicate. Most browsers currently operate at 60Hz (60 frames /s), which translates to around 16.6ms per frame. So what does the browser do during this frame (16.6ms)?

It is clear from the above diagram that a browser frame goes through the following processes:

  1. Accept input events
  2. Perform the event callback
  3. Start a frame
  4. Perform RAF (RequestAnimationFrame)
  5. Page layout, style calculation
  6. Draw the rendering
  7. Perform RIC (RequestIdelCallback)

The RIC event in step 7 is not executed at the end of each frame, but only after the first 6 things have been done in 16.6ms of a frame and there is still time left. If there is still time to execute RIC event after the execution of one frame, then the next frame can only continue rendering after the execution of the event. Therefore, RIC execution should not exceed 30ms. If the control is not returned to the browser for a long time, the rendering of the next frame will be affected, resulting in page lag and the event response is not timely.

RequestIdleCallback enlightenment

We use whether the browser has time left as a criterion for task interruption, so we need a mechanism to notify us when the browser has time left.

requestIdleCallback((deadline) = > {
// Deadline takes two parameters
  TimeRemaining (): How much time is left in the current frame
  // didTimeout: timeout or not
// If requestIdleCallback is followed by the second argument {timeout:... } forces the browser to execute after the current frame is executed.
 if (deadline.timeRemaining() > 0) {
   // TODO
 } else{ requestIdleCallback(otherTasks); }});Copy the code
// Examples of usage
var tasksNum = 10000

requestIdleCallback(unImportWork)

function unImportWork(deadline) {
  while (deadline.timeRemaining() && tasksNum > 0) {
    console.log(` performedThe ${10000 - tasksNum + 1}A task `)
    tasksNum--
  }
  if (tasksNum > 0) { // Continue execution in a future frame
    requestIdleCallback(unImportWork)
  }
}
Copy the code

Some browsers already implement this API, called requestIdleCallback. But Facebook dropped requestIdleCallback’s native API due to the following factors:

  • Browser compatibility;
  • The trigger frequency is unstable and affected by many factors. For example, when our browser switches between tabs, the requestIdleCallback that was registered with the previous TAB is triggered less frequently.

Reference: requestIdleCallback’s FPS is only 20

Based on the above reasons, has realized the functions in the React more complete requestIdleCallbackpolyfill, this is the Scheduler. In addition to the ability to trigger callbacks when idle, the Scheduler provides multiple scheduling priorities for tasks to set.

Reference: requestIdleCallback- Background task scheduling

5. Why is Fiber a leap forward in React performance?

What is the Fiber

Fiber is a thinner Thread than Thread and a more finely controlled execution model than Thread. In general computer science, Fiber is also a Cooperative programming model (coroutine) that helps developers arrange code in a way that is both modular and collaborative.

In React, Fiber is a new update mechanism implemented in React 16, which makes the React update process more controlled and eliminates the need for simultaneous recursion that affects performance.

Time fragmentation in React Fiber

A long task is divided into many small pieces, each of which takes a short time to run. Although the total time is still long, after each small piece is executed, other tasks are given a chance to execute, so that a single thread is not monopolized and other tasks still have a chance to run.

React Fiber is a fragmented update process. After each update process, it gives control back to the React mission coordination module to see if there are other urgent tasks to do, and if there are no urgent tasks to do, then do them.

Stack Reconciler

Based on the stack Reconciler, the browser engine starts at the top of the execution stack, pops up the current execution context, starts executing the next function, and doesn’t stop until the stack is cleared. Then you give the execution back to the browser. Because React treats the page view as the result of the execution of individual functions. Each page often consists of multiple views, which means multiple function calls.

If a page is complex enough, the resulting stack of function calls will be deep. For each update, the execution stack needs to be executed at one time, and nothing else can be done in the middle of it, except “single-minded”. In combination with the aforementioned browser refresh rate, JS is always executed, and the browser loses control, so it can’t start drawing the next frame in time. If this time is longer than 16ms, the animation will stall when the page needs to be animated because the browser cannot draw the next frame in time. Not only that, because the event response code is executed at the beginning of each frame, if the next frame is not drawn in time, the event response will be delayed.

Fiber Reconciler

Chain table structure

Instead of stack recursing before React 16, React Fiber uses linked list traversal. Linked lists are used extensively in React 16.

  • The tree structure is replaced by multidirectional linked list.
<div id="A">
  A1
  <div id="B1">
    B1
    <div id="C1"></div>
  </div>
  <div id="B2">
    B2
  </div>
</div>
Copy the code

  • Single linked list of side effects;

  • Status update singly linked list;

  • .

Linked list is a simple and efficient data structure. It holds a pointer to the next node in the current node. As you iterate, you find the next element by manipulating the pointer.

The advantages of linked lists over sequential structured data formats are:

  1. More efficient operations, such as reordering and deleting, simply change the pointer to the node.
  2. Not only can the next node be found according to the current node, but also its parent node or sibling node can be found in the multidirectional linked list.

But lists aren’t perfect, and here’s the downside:

  1. Takes up more space than sequential structure data because each node object also holds a pointer to the next object.
  2. Can’t read freely, must find his last node.

React trades space for time, and more efficient operations can easily be performed according to priority. The ability to find other nodes based on the current node plays a key role in the suspend and restore process mentioned below.

The Fibonacci sequence Fiber

Fibonacci sequence in recursive form:

function fib(n) {
  if (n <= 2) {
    return 1;
  } else {
    return fib(n - 1) + fib(n - 2); }}Copy the code

Use the same idea as Fiber to rewrite it as loop (this example is not equivalent to React Fiber) :

function fib(n) {
  let fiber = { arg: n, returnAddr: null.a: 0 }, consoled = false;
  // Mark the loop
  rec: while (true) {
    // When the expansion is complete, start the calculation
    if (fiber.arg <= 2) {
      let sum = 1;
      // Look for the parent
      while (fiber.returnAddr) {
        if(! consoled) {// Here print the fiber object in the form of a linked list
          consoled=true
          console.log(fiber)
        }
        fiber = fiber.returnAddr;
        if (fiber.a === 0) {
          fiber.a = sum;
          fiber = { arg: fiber.arg - 2.returnAddr: fiber, a: 0 };
          continue rec;
        }
        sum += fiber.a;
      }
      return sum;
    } else {
      / / a first
      fiber = { arg: fiber.arg - 1.returnAddr: fiber, a: 0}; }}}Copy the code

Vi. How can React Fiber achieve controllable update process?

The controllable update process is mainly reflected in the following aspects:

  • Task split
  • Tasks are suspended, resumed, and terminated
  • Tasks have priorities

Task split

In React Fiber mechanism, it adopts the idea of “breaking the whole into zero” and divides the large task of recursion traversing VDOM from Reconciler into several smaller tasks, each of which is responsible for processing only one node.

Tasks are suspended, resumed, and terminated

workInProgress tree

WorkInProgress represents the Fiber tree that is currently being updated. After Render or setState, a Fiber tree, also known as a workInProgress tree, will be built. This tree will collect the side effects of the current node when each node is built. When the whole tree is built, a complete chain of side effects will be formed.

currentFiber tree

CurrentFiber represents the Filber tree built in the last render. WorkInProgress is assigned to currentFiber after each update. The workInProgress tree is rebuilt on the next update, and the new workInProgress node is connected to the currentFiber node through alternate properties.

During the creation of the new workInProgress tree, Diff is performed against the corresponding node of currentFiber to collect side effects. It also reuse node objects corresponding to currentFiber, reducing the overhead of newly created objects. This means that creation, update, suspend, restore, and terminate all occur during the creation of the workInProgress Tree. The workInProgress Tree build process is essentially a loop of executing tasks and creating the next task.

hang

When the first small task is completed, it determines whether there is any idle time in this frame, suspends the execution of the next task, and gives control to the browser to execute the higher priority task, remembering the current suspended node.

restore

After the browser has rendered a frame, it determines if there is any time left for the current frame and, if so, resumes the suspended task. If there are no tasks to work on, the reconcile phase is complete and the render phase can begin.

  1. How do you tell if a frame has idle time?

Using the RIC (RequestIdleCallback) browser native API mentioned earlier, this method is polyfilled in the React source code for compatibility with older browsers.

  1. How do you know what the next task is when you resume?

The answer is in the previously mentioned linked list. Each task in React Fiber is actually processing a FiberNode object and then generating the FiberNode that the next task needs to process.

Termination of

Not every update makes it to the commit stage. When a new update is triggered during the reconciliation process, when the next task is executed, it determines whether there is a higher-priority task for execution, if so, it terminates the task to be executed, starts the new workInProgressFiber tree build process, and begins the new update process. This avoids repeated updates. This is why the lifecycle function componentWillMount is likely to be executed multiple times after React 16.

Tasks have priorities

In addition to controlling updates via suspend, resume, and terminate, React Fiber assigns priority to each task. When a FiberNode is created or updated, the algorithm assigns an expirationTime to each task. At the time of execution of each task, in addition to determining the remaining time, if the current processing node has expired, the task must be executed regardless of whether there is free time. The size of the expiration time also represents the priority of the task.

The mission collects side effects for each FiberNode as it executes, A single linked list A1(TEXT)-B1(TEXT)-C1(TEXT) -C1-c2 (TEXT)-C2-B1-B2(TEXT) -B2-a is formed for nodes with side effects through firstEffect, lastEffect and nextEffect.

In fact, the end is to collect this list of side effects, with it, in the next rendering phase by traversing the chain of side effects to complete DOM update. It is important to note that the action of updating the real DOM is done in one go and cannot be interrupted, otherwise it will cause a visual commit.

<div id="A1">
  A1
  <div id="B1">
    B1
    <div id="C1">C1</div>
    <div id="C2">C2</div>
  </div>
  <div id="B2">
    B2
  </div>
</div>
Copy the code

Visual display

Based on these processes, using Fiber, we have two comparisons that we often see in the community.

Clear display and interaction, source code can be accessed through the following two links, view the source code of the web page.

  • Stack Example
  • Fiber Example

What does the Fiber structure look like?

Incremental updates based on time sharding require more context information than the previous vDOM tree could satisfy, so the fiber tree is extended. The update process is to construct a new Fiber tree (workInProgress Tree) based on the input data and the existing Fiber tree.

There are many attributes on FiberNode. According to the author’s understanding, the following attributes are worth paying attention to: Return, Child, Sibling (mainly responsible for the link of fiber list); StateNode; EffectTag; ExpirationTime; Alternate; NextEffect. See Class FiberNode below for an overview of the properties:

class FiberNode {
  constructor(tag, pendingProps, key, mode) {
    // Instance properties
    this.tag = tag; // Mark different component types, such as function components, class components, text components, native components...
    this.key = key; // The key on the react element is the same as the key on the JSX element
    this.elementType = null; // The first argument to createElement, the type on the ReactElement
    this.type = null; // Represents the real type of fiber. ElementType is basically the same, but may be different when lazy loading is used
    this.stateNode = null; // The instance object, such as the new class component, is mounted on this property. In the case of RootFiber, it is mounted on FiberRoot, and in the case of native nodes, it is mounted on DOM objects
    // fiber
    this.return = null; // The parent node points to the last fiber
    this.child = null; // The child node points to the first fiber below itself
    this.sibling = null; // The sibling component points to a sibling node
    this.index = 0; // If there is no sibling node, it is 0. If a parent node has an array type, each child node is given an index. The index and key are diff together
    this.ref = null; // reactElement on the ref attribute
    this.pendingProps = pendingProps; / / new props
    this.memoizedProps = null; / / the old props
    this.updateQueue = null; // A setState is executed on the update queue on fiber to attach a new update to the property. Each update will eventually form a linked list structure, which will be updated in batches
    this.memoizedState = null; // This corresponds to memoizedProps, the last rendered state, which corresponds to the current state
    this.mode = mode; // represents the rendering mode of the subcomponents under the current component
    // effects
    this.effectTag = NoEffect; // Indicate what kind of update (update, delete, etc.)
    this.nextEffect = null; // Point to the next fiber to be updated
    this.firstEffect = null; // Point to the first of all child nodes in the fiber that needs updating
    this.lastEffect = null; // Point to the last fiber of all child nodes that needs to be updated
    this.expirationTime = NoWork; // Expiration time, which represents a point in the future when the task should be completed
    this.childExpirationTime = NoWork; // Child expiration time
    this.alternate = null; // A reference between the current tree and the workInprogress tree}}Copy the code

Credit: Fully understand React Fiber

function performUnitWork(currentFiber){
    //beginWork(currentFiber) // start work (currentFiber
  // If he has a son, he returns to his son
  if(currentFiber.child){
    return currentFiber.child;
  } 
  // If you don't have a son, find a brother
  while(currentFiber){// Keep looking up
    //completeUnitWork(currentFiber); // Attach your side effects to the parent node
    if(currentFiber.sibling){
      returncurrentFiber.sibling } currentFiber = currentFiber.return; }}Copy the code

Concurrent Mode

Concurrent Mode refers to a new Mode that is enabled when React takes advantage of new features introduced by the previous Fiber. React17 supports concurrent Mode, which is a new set of features including Fiber, Scheduler, and Lane that adjust application response speed based on user hardware performance and network conditions. The core is for asynchronous interruptible updates. Concurrent Mode is also the main direction of future react iterations.

The React experimental version currently allows users to choose from three modes:

  1. Legacy Mode: Equivalent to the current stable version of the Mode
  2. Blocking Mode: should be a long-standing Mode that will replace Legacy Mode
  3. Concurrent Mode: the Mode that becomes default later

Concurrent Mode actually turns on a bunch of new features, but two of the most important ones solve the two problems we mentioned at the beginning:

  1. Suspense: Suspense is a mechanism for asynchronous processing provided by React, not a specific data request library. It is a native component asynchronous call primitive provided by React.
  2. useTrasition: Let the page implementPending -> Skeleton -> Complete, users can stay on the current page when switching pages, so that the page remains responsive. Rather than showing a blank page or loading state that is uselessThe user experienceMore friendly.

Suspense can be used to solve the problem of blocking requests, and the problem of UI stalling can be solved by using concurrent mode, but it still takes some code changes to use concurrent mode to achieve friendlier interactions.

References: introduce Concurrent Mode (experimental) | understand the React Fiber & Concurrent Mode | 11. Concurrent Mode (what is Concurrent Mode) | React that everyone can read the source code parsing

It should be a future

Concurrent Mode is only Concurrent, since the task can be split (as long as the full effect list ends up), it allows parallel execution (multiple Fiber Reconcilers + multiple workers), and the first screen is easier to load/render in chunks (vDOM forest).

For parallel rendering, it’s said that Firefox tests show that a 130ms page can be done in 30ms, so that’s something to look forward to, and React is ready for it. This is one of the more features you’ll hear about unlock in the React Fiber context.

IsInputPending — The impact of Fiber architecture idea on front-end ecology

Facebook introduced and implemented the isInputPending() API in Chromium, which improves the responsiveness of web pages, but does not significantly affect performance. The isInputPending API proposed by Facebook was the first to apply the concept of interrupts to browser user interactions and allow JavaScript to check the event queue without handing control over to the browser.

Currently, the isInputPending API is only available in Chromium version 87 and is not implemented in other browsers.

Reference: Facebook is implementing React optimizations into the browser! | Faster – input events with Facebook ‘s first browser API contribution

The impact of Svelte on the inherent pattern

In the current front-end field, React, Vue, and Angular are gradually stabilizing. If we say that what frameworks will appear in the front-end industry may challenge React or Vue? Many people think Svelte should be one of those options.

Svelte is [Svelte], which means slim and slender, and is a hot new front end frame. It ranks among the best in terms of developer satisfaction, interest, and market share. It also has a smaller package size, less development code to write, and it has scored well in performance reviews compared to React and Vue.

The core idea of Svelte is to “reduce the amount of code the framework runs through static compilation.”

What are Svelte advantages

  • No Runtime — No Runtime code
  • Less-code — write Less Code
  • Hight-performance — High Performance

Svelte disadvantage

  • community
  • community
  • community

The principle of the overview

Svelte analyzes the relationship between data and DOM nodes at compile time, and updates DOM nodes efficiently when data changes.

  • Rich Harris did not use Virtual DOM in the design of Svelte, mainly because he felt that the process of Virtual DOM Diff was very inefficient. For details, see is the Virtual Dom really efficient? Svelte uses a Templates syntax to optimize during compilation;
  • Svelte records dirty data using the following methods: bitMask.
  • Correspondence between data and DOM nodes: React and Vue diff the Virtual DOM to figure out which DOM nodes are most efficient to update. Svelte records the correspondence between data and DOM nodes at compile time and stores it in the P function.

Reference: Svelte, the emerging front-end framework, from introduction to principle

Reference and recommendation

  • React Technology revealed
  • Front End Engineer self-cultivation: How does React Fiber make the update process manageable
  • Svelte, the emerging front-end framework, goes from inception to principle
  • React: Framework and performance (part 2)

This article is posted on my personal blog, welcome corrections and star.