React Fiber: How does the React Fiber update process become controllable

preface

Since React 16, React uses Fiber mechanism to replace the original VDOM recursive traversal based on native execution stack, which improves page rendering performance and user experience. At first glance, Fiber sounds like a mystery, but when you don’t even understand the native implementation stack, you can still write code happily. Don’t panic, Iron! Now let’s talk about Fiber.

What is the Fiber

Fiber, which means “Fiber” in English, is a thinner line than Thread and a more precise execution model than Thread. Fiber is also a Cooperative programming model in the broad sense of computer science, helping developers orchestrate code in a modular and collaborative way.

In short, Fiber is a new update mechanism implemented by React 16 that makes React updates manageable and avoids the problem of a bunch of recursions affecting performance.

The basics you need to know about Fiber

1 Browser refresh rate (frames)

The content of the page is drawn frame by frame, and the browser refresh rate represents how many frames the browser draws per second. Currently, most browsers run at 60 hz (60 frames per second), and each frame takes about 16ms. In principle, the number of frames drawn in 1s is also much, and the picture performance is delicate. So what did the browser do during this frame (16ms)?

As you can clearly see from the above figure, 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. Apply colours to a drawing
  7. Perform RIC (RequestIdelCallback)

The RIC event in step 7 is not executed at the end of every frame, but only after the first 6 things have been done and there is still time left in a frame of 16ms. Here the way, if a frame RIC executed have time after the event, then the next frame need to continue to the end of the event to perform rendering, not more than 30 ms so RIC implementation, if the control back to the browser will not be for long time, will affect the next frame render, prompted a caton page and incident response in a timely manner.

2. JS native stack

Before React Fiber, React recursively traversed the VDOM via the native execution stack. When the browser engine encounters JS code for the first time, it generates a global execution context and pushes it onto the execution stack, and then pushes a new context onto the stack with each subsequent function call. Such as:

function A(){
  B();
  C();
}
function B(){}
function C(){}
A();
Copy the code

When the engine executes, it forms an execution stack like this:

The browser engine starts at the top of the stack, pops up the current execution context, and starts executing the next function until the stack is empty. It then gives execution back to the browser. React treats the page view as the result of a function execution. Each page tends to consist of multiple views, which means multiple function calls.

If a page is complex enough, the resulting function call stack can be very deep. For each update, the stack needs to be executed all at once, with nothing to do but “focus” in the middle. Combined with the previously mentioned browser refresh rate, JS is always executing and the browser is not in control, so it can’t start drawing the next frame in time. If this takes longer than 16ms, the animation will stall when the page needs to be animated because the browser can’t draw the next frame in time. Not only that, because the event response code is executed at the beginning of each frame, the event response is delayed if the next frame cannot be drawn in time.

3. Time Slicing

Time sharding refers to a scheme that puts multiple small-grained tasks into a time slice (a frame) for execution. React Fiber puts multiple tasks into a time slice for execution.

4. List

React Fiber replaces React 16 stack recursion with linked list traversal. React 16 uses a lot of linked lists. Such as:

  • The tree structure is replaced by a multi-way linked list

For example, the following component:

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

A linked list like the following is used:

  • Single linked list of side effects

  • Single linked list of status updates

  • .

A linked list is a simple and efficient data structure that holds Pointers to the next node in the current node, like a train running from one node to the next

As you traverse, you manipulate the pointer to find the next element. But be careful when manipulating Pointers (reordering and pointing).

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

  1. More efficient operations, such as reordering, deleting, just need to change the pointer to the node is good.
  2. You can not only find the next node based on the current node, but also find its parent node or brother node in the multiway linked list.

But linked lists are not perfect.

  1. It takes up more space than sequential structured data because each node object also holds a pointer to the next object.
  2. Cannot read freely, must find its last node.

React uses space for time, and more efficient operations make it easier to operate based on priorities. The ability to locate other nodes based on the current node also plays a key role in the suspension and recovery process mentioned below.

How does React Fiber make the update process controllable?

React Fiber Allows you to control the React Fiber update process.

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

  1. Task split
  2. Task suspends, resumes, and terminates
  3. Tasks have priority

1. Split tasks

As mentioned earlier, React Fiber was based on the native execution stack, and each update would occupy the main thread until the update was complete. This can cause event response delays, animation stuttering, and more.

In the React Fiber mechanism, it employs a strategy of “breaking the Reconciler into pieces,” dividing the large task of recursively traversing the VDOM into a number of smaller tasks, each dealing with a single node. Such as:

import React from "react";
import ReactDom from "react-dom"
const jsx = (
    <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>
)
ReactDom.render(jsx,document.getElementById("root"))
Copy the code

This component is divided into eight small tasks during rendering, and each task is used to process A1(div), A1(text), B1(div), B1(text), C1(div), C1(text), C2(div), C2(text), B2(div), B2(text). Through time slice, perform one or more tasks in a time slice. It should be mentioned that all small tasks are not shard all at once, but the next task is generated while the current task is being processed. If no next task is generated, the rendering Diff operation is completed.

2. Suspend, resume, and terminate

The workInProgress tree and currentFiber Tree must be mentioned before suspending, resuming, and terminating.

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

CurrentFiber represents the Filber tree built in the last rendering. WorkInProgress is assigned to currentFiber after each update. In the next update, the workInProgress tree is rebuilt, and the node of the new workInProgress is connected to the node of currentFiber through the alternate property.

During the creation of the new workInProgress tree, Diff comparison is performed with the corresponding nodes of currentFiber to collect side effects. Node objects corresponding to currentFiber will also be reused to reduce the overhead of newly created objects. This means that suspend, restore, and terminate operations occur during the creation of the workInProgress Tree, whether it is created or updated. The workInProgress Tree construction process is essentially a loop of executing tasks and creating the next task. The general process is as follows:

When there are no next tasks to execute, the workInProgress Tree is built and the commit phase begins to complete the actual DOM update.

During the construction of workInProgressFiber Tree, the update process can be controlled by suspending, resuming, and terminating tasks. The following simplified the source code, roughly as follows:

let nextUnitWork = null;// Next execution unit
// Start scheduling
function shceduler(task){
     nextUnitWork = task; 
}
// Loop the work
function workLoop(deadline){
  let shouldYield = false;// Whether to relinquish control of the time slice
  while(nextUnitWork && ! shouldYield){ nextUnitWork = performUnitWork(nextUnitWork) shouldYield = deadline.timeRemaining()<1 // Run out of time, check out control to the browser
  }
  if(! nextUnitWork) { conosle.log("All missions completed.")
    //commitRoot() // Commit update view
  }
  // If there are still tasks, but after handing over control, request the next schedule
  requestIdleCallback(workLoop,{timeout:5000})}/* * handles a small task, which is essentially a Fiber node, and returns the next task if there are any remaining tasks
function performUnitWork(currentFiber){...return FiberNode
}
Copy the code

hang

When the first small task is complete, determine whether there is any free time in this frame, suspend the next task, remember the current suspended node, and give control to the browser to execute the higher-priority task.

restore

After the browser has rendered a frame, it determines whether the current frame has any time left, and if so resumes the pending task. If there are no tasks to deal with, the blending phase is complete and the rendering phase can begin. This perfectly solves the problem that the harmonic process has been occupying the main thread.

So the question is how does he tell if a frame has any free time? The answer is the RIC (RequestIdleCallback) browser native API we mentioned earlier. The React source code polyfills this method for compatibility with older browsers.

How do you know what the next task is when you resume execution? The answer is in the linked list mentioned earlier. In React Fiber, each task processes a FiberNode object and then generates a FiberNode for the next task to process. FiberNode, by the way, is a data format, and here’s what FiberNode looks like without its face on:

class FiberNode {
  constructor(tag, pendingProps, key, mode) {
    // Instance properties
    this.tag = tag; // Mark different component types, such as function component, class component, text, native component...
    this.key = key; // The react element key is the same as the JSX key, which is the final ReactElement key
    this.elementType = null; // The first argument to createElement, type on ReactElement
    this.type = null; // Represents the actual type of fiber. ElementType is basically the same, but may be different if lazy loading is used
    this.stateNode = null; // The instance object, such as the class component new, is mounted on this property, which is FiberRoot if it is RootFiber, or dom object if it is native
    // fiber
    this.return = null; // Parent node, pointing to the previous fiber
    this.child = null; // The child node points to the first fiber below itself
    this.sibling = null; // Sibling component, pointing to a sibling node
    this.index = 0; If there are no siblings, each child is given an index, and the index and key are diff together
    this.ref = null; // ref attribute on reactElement
    this.pendingProps = pendingProps; / / new props
    this.memoizedProps = null; / / the old props
    this.updateQueue = null; // A single setState execution on the update queue on fiber will attach a new update to the property, and each update will eventually form a linked list, which will eventually be updated in batches
    this.memoizedState = null; // For memoizedProps, the state rendered last time is equivalent to the current state, understood as the relationship between prev and next
    this.mode = mode; // Represents how the children of the current component are rendered
    // effects
    this.effectTag = NoEffect; // Indicates what update fiber is currently doing
    this.nextEffect = null; // Point to the next fiber update
    this.firstEffect = null; // Point to the first of all child nodes in fiber that needs to be updated
    this.lastEffect = null; // Point to the last fiber of all the child nodes that needs to be updated
    this.expirationTime = NoWork; // Expiration time, which represents at what point in the future the task should be completed
    this.childExpirationTime = NoWork; // child expiration time
    this.alternate = null; // References between the current tree and the workInprogress tree}}Copy the code

Er… It looks like it’s going to go to the top, and here’s the beauty look:

Isn’t it better? At each time of the loop, the next node to execute needs to be processed.

function performUnitWork(currentFiber){
    //beginWork(currentFiber) // Find the son and attach it to currentFiber by way of a linked list
  If you have a son, return to your son
  if(currentFiber.child){
    return currentFiber.child;
  } 
  // If there is no son, find a brother
  while(currentFiber){// Go straight up
    //completeUnitWork(currentFiber); // Attach your side effects to the parent node
    if(currentFiber.sibling){
      returncurrentFiber.sibling } currentFiber = currentFiber.return; }}Copy the code

Returns the child or sibling or parent of the processing node at the end of a task. As long as a node returns, there is another task, and the object of the next task is the returned node. Remember the current task node through a global variable, and when the browser is idle again, use this global variable to find the node that its next task needs to work on and resume execution. The loop continues until no nodes need to be processed return, indicating that all tasks have been completed. Finally, we all hold hands and form a Fiber tree.

Termination of

Not every update goes to commit. When a new update is triggered during reconciliation, the next task is executed to determine whether there is a higher priority task to be executed. If there is, the original task to be executed is terminated and a new workInProgressFiber tree building process is started to start the new update process. This avoids repeated update operations. This is why after React 16 the life cycle function componentWillMount may be executed multiple times.

3. Tasks have priority

React Fiber assigns priority to each task in addition to controlling updates by suspending, resuming, and terminating. When FiberNode is created or updated, each task is algorithmically assigned a expirationTime. At the time of each task execution, in addition to determining the remaining time, if the current processing node has expired, then 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 collected side effects from each FiberNode as it went along, The nodes with side effects are formed into A single linked list of side effects through firstEffect, lastEffect and nextEffect AI(TEXT)-B1(TEXT)-C1(TEXT) -C2(TEXT) -B1-B2(TEXT) -B2-a.

In the end, it’s all about collecting this side effect list, which can be used to update the DOM by iterating through the side effect chain in the subsequent render phase. It is important to note that updating the real DOM is done in one go and should not be interrupted otherwise it will create visual incoherence.

React Fiber thoughts

1. Can generaters be used instead of linked lists

One of the most important aspects of the Fiber mechanism is the need to implement suspend and resume. From an implementation point of view, generator can also implement this. So why is the official not using generator? The guess is performance. Not only do generators give you ground in the middle of the stack, they also have to wrap each function in a generator. This adds a lot of syntactic overhead on the one hand, as well as run-time overhead for any existing implementation. Performance is not nearly as good as linked lists, and linked lists do not require browser compatibility.

2. Will Vue use Fiber to optimize updates to complex pages

This question is a bit confusing. If Vue does this, is it an admission that Vue is “integrating” the best of Angular and React? React has Fiber, Vue has to have Fiber?

Both rely on DOM Diff, but their implementation is different. The purpose of DOM Diff is to collect side effects. Vue implements dependency collection through Watcher, which is a good optimization in itself. So it doesn’t matter that Vue doesn’t use Fiber.

conclusion

React Fiber is the introduction of a central midfield commander in charge of the update process, known in football as the forward. Apart from the performance and efficiency gains, this idea of “breaking the whole into pieces” and scheduling tasks can be applied to our everyday architectural design.

Recommended reading

V8 engine garbage collection and memory allocation

How can junior engineers grow quickly and seek breakthroughs

, recruiting

ZooTeam, a young passionate and creative front-end team, belongs to the PRODUCT R&D department of ZooTeam, based in picturesque Hangzhou. The team now has more than 40 front-end partners, with an average age of 27, and nearly 30% of them are full-stack engineers, no problem in the youth storm group. The members consist of “old” soldiers from Alibaba and netease, as well as fresh graduates from Zhejiang University, University of Science and Technology of China, Hangzhou Electric And other universities. In addition to daily business docking, the team also carried out technical exploration and practice in material system, engineering platform, building platform, performance experience, cloud application, data analysis and visualization, promoted and implemented a series of internal technical products, and continued to explore the new boundary of front-end technology system.

If you want to change what’s been bothering you, you want to start bothering you. If you want to change, you’ve been told you need more ideas, but you don’t have a solution. If you want change, you have the power to make it happen, but you don’t need it. If you want to change what you want to accomplish, you need a team to support you, but you don’t have the position to lead people. If you want to change the pace, it will be “5 years and 3 years of experience”; If you want to change the original savvy is good, but there is always a layer of fuzzy window… If you believe in the power of believing, believing that ordinary people can achieve extraordinary things, believing that you can meet a better version of yourself. If you want to be a part of the process of growing a front end team with deep business understanding, sound technology systems, technology value creation, and impact spillover as your business takes off, I think we should talk. Any time, waiting for you to write something and send it to [email protected]