Why fiber was introduced

React’s philosophy is to respond quickly. React’s philosophy is to respond quickly. But as we all know, JavaScript runs in a single thread, and in a cycle of events, the browser passes through the following stages:

Select the most urgent Task from the Task queue -> Clear microTask -> perform UI Render operation (may be skipped)Copy the code

When the browser is currently executing a large Task that takes more than 50 milliseconds to execute, the Task is called a Long Task. As mentioned in the Event Loop Processing Model, a certain amount of time should be set aside for UI render in a single frame; otherwise, excessively long JS execution will lead to insufficient remaining time in a single frame to perform UI render operation in the current event loop. The UI Render phase will be skipped, resulting in a stutter where the user will feel that the application is not responsive enough, which is not in keeping with the React philosophy.

React (reactjs.org) explains that the reason for stalling is simple: once a rendering has started, it cannot be stopped. If only we could turn a render that could not be broken into a render that could be broken, the problem would be solved. React introduces a Concurrent mode in React 16. The fundamental unit of work for Concurrent Mode is Fiber.

React 15 architecture

React 15’s architecture is divided into two layers:

  • Reconciler
  • Render

The Reconciler is responsible for finding the Reconciler’s changing components, and Render is responsible for rendering the Reconciler’s changing components onto the page, working alternately.

The React 15 Reconciler is called a Stack Reconciler, and works based on recursion, a sequential traversal of an N-fork tree. Here’s a simple example to get an overview of how React 15 works.

class Demo extends React.Component { constructor(... props) { super(... props); this.state = { num: 1 }; } onClick() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <button onClick={() => this.onClick()}>add button</button> <p>{0 + this.state.num}</p> <p>{1  + this.state.num}</p> <p>{2 + this.state.num}</p> </div> ); }}Copy the code

In the demo, when the add button is clicked, the value of count is increased by 1. The general process of page update is as follows:

  1. Reconciler found the firstpThe text node under1Need to update to2Notify Render that the text node needs to be updated to2;
  2. Render updates this text node to2;
  3. Reconciler found the secondpThe text node under2Need to update to3Notify Render that the text node needs to be updated to3;
  4. Render updates this text node to3;
  5. Reconciler found the secondpThe text node under3Need to update to4Notify Render that the text node needs to be updated to4;
  6. Render updates this text node to4;

React 15 doesn’t support the idea of fast response

  • The Stack Reconciler is prone to gridlock and difficult to disrupt

Once the Stack Reconciler is executed, it is recursively traversed down the n-fork tree starting at the root node until the traversal is complete. The whole coordination process is synchronous and continuous. If the hierarchy of the tree is too deep, it will inevitably result in stagnation. This recursion is also highly stack dependent. The system call stack is hard to break and can’t be easily recovered in the field. If you need to recover to the current location of the interrupt, you have to do it all over again.

  • Coordination is performed alternately with rendering

As we can see in the workflow of React, interruptions in the middle of the work between Render and Reconciler can result in incomplete view updates, giving users the feeling that the app is out of order.

Architecture after Act16

After React16, the React architecture became three tiers

  • Scheduler
  • Reconciler:
  • Renderer

On the basis of the original two-tier architecture, a Scheduler layer is added, which is responsible for updating the priority of scheduling tasks

Scheduler

React needs a mechanism to tell us when we need to interrupt the current task. As a result, React introduces Scheduler to schedule and prioritize update tasks.

Reconciler

React16 converts the Stack Reconciler of React15 into Fiber Reconciler, which we will cover in more detail later.

Renderer

Render is still responsible for synchronizing the Reconciler’s changed components onto the page, but unlike before, Renderer and Reconciler are implemented in stages.

What is the fiber

Act 16 introduced the concept of fiber, also known as fiber.

After the introduction of Fiber, the Reconciler is called Fiber Reconciler, and fiber contains a number of meanings:

As a unit of work

Fiber is the unit of work in coordination. After Act16, the process of coordination has changed from recursion to a cycle that can be interrupted.

Function workLoopConcurrent() {// Perform tasks until Scheduler asks us to yield // While (workInProgress! == null && ! shouldYield()) { workInProgress = performUnitOfWork(workInProgress); }}Copy the code

When React finishes executing a unit of work, it checks to see if there is time available for the next unit of work. If there is, it continues to execute the next unit of work. If there is no more time, it gives control back to the browser and resumes rendering when the browser is idle.

As a data structure

Fiber can also be considered as a data structure. React transforms the VDOM tree from an N-fork tree structure to a multiple-intersecting linked list structure composed of fiber nodes. Let’s look at the attribute definition of the Fiber node:

function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) {// This. Tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // This. Return = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; // This. PendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null; this.mode = mode; this.effectTag = NoEffect; this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; This. Lanes = NoLanes; this.childLanes = NoLanes; This. Alternate = null; // This. Alternate = null; }Copy the code

Let’s focus on the following three attributes:

// Point to parent Fiber node this.return = null; // Point to the subfiber node this.child = null; This.sibling = null;Copy the code

These three attributes place the Fiber node in three one-way linked lists:

  • Child list
  • Sibling linked list
  • Return linked list (parent linked list)

The linked list of each fiber node intersects and combines to form the Fiber tree.

Let’s look at a simple example:

function App() {
  return (
    <div>
      father
      <div>child</>  
    </div>
  )
}
Copy the code

The corresponding Fiber tree is:

Why fast response with Fiber

Interrupts gracefully

Fiber tree traversal: fiber tree traversal: Fiber tree traversal

Function workLoopConcurrent() {// Perform tasks until Scheduler asks us to yield // While (workInProgress! == null && ! shouldYield()) { workInProgress = performUnitOfWork(workInProgress); } } function performUnitOfWork(unitOfWork: Fiber): void { //... let next; / /... Next = beginWork(current, unitOfWork, subtreeRenderLanes) next = beginWork(current, unitOfWork, subtreeRenderLanes) / /... // If there is no child node, If (next === null) {// If this doesn't spawn new work, // Inside this function will help us find the next executable node completeUnitOfWork(unitOfWork); } else { workInProgress = next; } / /... } function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; do { //... Const siblingFiber = completedwork.sibling; if (siblingFiber ! == null) {// If there is more work to do in this returnFiber, do that next. Continue with performUnitOfWork, execute the current node and try traversing the child list of the current node. return; } // Otherwise, return to the parent; // If no sibling exists, go back to the parent and try to find the sibling of the parent completedWork = returnFiber; // Update the next thing we're working on in case something throws. workInProgress = completedWork; } while (completedWork ! == null); / /... }Copy the code

It can be seen that React uses multiple one-way traversal of child list, Sibling list and return list to replace the antecedent traversal of n-tree. In coordination, we no longer need to rely on the system call stack. Because unidirectional list traversal is strictly list oriented, and each node has a unique next node, we do not need to maintain the collation call stack to recover the interruption in the event of an interruption. We only need to secure the reference to the fiber node where the interrupt occurred, and when the interrupt is resumed we can continue through the next node (whether it is Child or Sibling or return).

Time slicing

Time slicing is actually a simulation implementation of requestIdleCallback, and we will discuss how to implement it later when we are free (digging holes). One of the conditions for performing the next performUnitOfWork in workLoopConcurrent in the coordination loop we described above is whether shouldYield returns true. Based on the result returned by the Scheduler, we can determine whether the browser currently has time available to execute the next unit of work, thus achieving time sharding

As you can see, with the introduction of Fiber, we split the coordination process into fiber node execution processes, and the coordination process can be gracefully interrupted. When the browser does not have more time to coordinate, we can interrupt the current update task. And continue traversing the Fiber Tree again from the current work unit when the browser is idle.

At the same time, since the three stages of Scheduler, Reconciler and Render are executed separately after React16, the operations of Scheduler and Reconciler will not be mapped to the view until the COMMIT stage, so incomplete view updates will not occur.

Double buffering mechanism

The dual-cache mechanism is a technique that builds and replaces directly in memory. React uses this technique in its coordination process.

There are two Fiber trees in React. One is the current Fiber Tree corresponding to the DOM displayed on the screen, and the other is the workInProgress Fiber Tree constructed in memory when a new update task is triggered.

Fiber nodes in the Current Fiber Tree and workInProgress Fiber Tree are connected using the alternate property.

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
Copy the code

The React root node also has the Current attribute. Use the current attribute to switch between the root nodes of different Fiber trees to switch between the Current Fiber Tree and workInProgress Fiber Tree.

In the coordination phase, React uses the DIff algorithm to compare the React Element that generates the update with the nodes in the current Fiber Tree, and finally generates the workInProgress Fiber Tree in memory. The Renderer then renders the update to the page based on the workInProgress Fiber Tree. At the same time, the current attribute of the root node points to the workInProgress Fiber Tree, and the workInProgress Fiber Tree becomes the Current Fiber Tree.

Why did you upgrade to Act16 + and not feel the difference with Act15?

React currently has multiple modes, since most applications still use reactdom. render(
, rootNode) as the entry function, this will enable Legacy mode for React. In Legacy mode, shouldYield is not used when traversing the Fiber tree, so synchronous updates are still used:

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}
Copy the code

To enable asynchronous updates, use the reactdom.createroot (rootNode).render(
) entry function to enable concurrent mode. React currently has multiple modes, each of which can be enabled using a Concurrent mode:

And the characteristics of each mode:

You can read the original text to understand.