preface

React Fiber (Reactv16.8.6) Reacts Fiber (Reactv16.8.6) Reacts Fiber (Reactv16.8.6) Reacts Fiber

This post is simultaneously posted on my Github personal blog

Fiber appears in the background

The first thing to know is that the JavaScript engine and the page rendering engine are mutually exclusive, so when one thread executes, the other can only hang and wait.

Under such a mechanism, if JavaScript threads occupy 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 poor responsiveness of the page and the user may feel stuck.

This is the problem with the Stack Reconciler in React 15, which is the time-out of JavaScript on the main thread. The Stack Reconciler is a synchronous recursive process that uses the JavaScript engine’s own function call Stack, which executes until the Stack is empty, so when React renders components, the process is seamless from start to completion. If the rendered component is large, js execution will occupy the main thread for a long time, resulting in poor page responsiveness.

In addition, all tasks are carried out in sequence without priority. As a result, tasks with higher priorities cannot be carried out first.

What is the Fiber

The Chinese translation of Fiber is called the Fiber process. Fiber is a process that is even thinner than thread. Fibers are intended to provide finer control over the rendering process implementation.

From an architectural perspective, Fiber is a rewrite of the React core algorithm, known as 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.

A fiber is a JavaScript object with the following data structure:

type Fiber = {
  // The WorkTag type used to mark Fiber indicates the component type represented by fiber, such as FunctionComponent and ClassComponent
  tag: WorkTag,
  // ReactElement key
  key: null | string,
  // ReactElement. Type, the first argument to call createElement
  elementType: any,
  // The resolved function/class/ associated with this fiber.
  // Indicates the node type currently represented
  type: any,
  // Represents the element component instance corresponding to the current FiberNode
  stateNode: any,

  // Point to 'parent' in the Fiber tree to return up after processing the node
  return: Fiber | null.// point to your first child node
  child: Fiber | null.// The return of the sibling node points to the same parent node
  sibling: Fiber | null.index: number,

  ref: null | (((handle: mixed) = > void) & { _stringRef: ?string }) | RefObject,

  // The component props object in the current process
  pendingProps: any,
  // Props after the last rendering
  memoizedProps: any,

  // Updates generated by components of the Fiber are stored in this queue
  updateQueue: UpdateQueue<any> | null.// State from the last rendering
  memoizedState: any,

  // a list of fiber-dependent contexts
  firstContextDependency: ContextDependency<mixed> | null.mode: TypeOfMode,

  // Effect
  // To record Side Effect
  effectTag: SideEffectTag,

  // Single linked lists are used to quickly find the next side effect
  nextEffect: Fiber | null.// The first side effect in the subtree
  firstEffect: Fiber | null.// Last side effect in the subtree
  lastEffect: Fiber | null.// represents at what point in the future the task should be completed, after which the version was renamed to lanes
  expirationTime: ExpirationTime,

  // Quickly determine if there are any changes in the subtree that are not waiting
  childExpirationTime: ExpirationTime,

  // Fiber version pool, which records the fiber update process for easy recovery
  alternate: Fiber | null,}Copy the code

In May 2020, the priority model represented by the expirationTime attribute was replaced by Lanes.

How does Fiber solve the problem

Fiber breaks up a render task into multiple render tasks, rather than doing them all at once. It treats each finely divided task as an “execution unit”. React checks how much time is left and cedes control if there isn’t, so the task is spread over multiple frames. In the middle, you can return to the main process to control the execution of other tasks, and ultimately achieve a smoother user experience.

After recovery, the intermediate state can be reused, and different tasks can be assigned different priorities. Each task update unit is the Fiber node corresponding to the React Element.

Realization principle of Fiber

The way it was implemented was with the requestIdleCallback API, but the React team polyfilled it, making it more compatible and extending features than the native browser.

Window. RequestIdleCallback () method will be called function in browser free time line. This enables developers to perform background and low-priority work on the main event loop without affecting the delay of critical events such as animations and input responses. Functions are typically executed in first-come-first-called order; however, if a callback function specifies a timeout, it is possible to scramble the order of execution in order to execute the function before it times out.

The requestIdleCallback callback is executed only if the current browser is idle.

The purpose of requestIdleCallback is to perform low-priority tasks during the remaining free time of a browser frame. First, React tasks are divided into multiple steps and completed in batches. After completing a portion of the task, hand back control to the browser, giving it time to render the page again. React allows the browser to run out of time and then proceed with tasks left unfinished by React. This is a cooperative scheduling approach.

In short, the browser assigns us a slice of execution time, and we execute within this agreed time frame, giving control back to the browser.

The React 16 Reconciler is based on Fiber nodes and is called Fiber Reconciler.

As a static data structure, each Fiber node corresponds to a React Element, which stores the type of the component (function component/class component/native component, etc.) and the corresponding DOM node.

As a dynamic unit of work, each Fiber node holds the component’s changed state and work to be performed during this update.

Each Fiber node has a React element. How do multiple Fiber nodes connect to form a tree? By the following three attributes:

// point to the parent Fiber node
this.return = null
// Point to the sub-fiber node
this.child = null
// Point to the first sibling Fiber node on the right
this.sibling = null
Copy the code

Fiber architecture core

The Fiber architecture can be divided into three layers:

  • Scheduler — Scheduling tasks with priority, and high-priority tasks with priority into Reconciler
  • Reconciler Reconciler — is responsible for identifying the components of change
  • Renderer — Responsible for rendering the changing components onto the page

Compared to React15, React16 has a Scheduler that schedules the priority of updates.

Under the new architecture pattern, the workflow is as follows:

  • 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 rendering, A new round of scheduling begins, and the previously interrupted Task A is pushed back into the Reconciler layer to continue its rendering journey, which is “recoverable.”

The core of Fiber architecture is “interruptible”, “recoverable”, “priority”

The Scheduler Scheduler

This requires the requestIdleCallback mentioned above. The React team implemented the more fully featured requestIdleCallback Polyfill, which is called Scheduler. In addition to the ability to trigger callbacks when idle, Scheduler provides a variety of scheduling priorities for tasks to set.

The Reconciler coordinator

React 15 is a recursive process for the virtual DOM. React 16 is an interruptible loop. Each loop calls shouldYield to determine whether there is any time left.

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while(workInProgress ! = =null && !shouldYield()) {
    // workInProgress represents a tree of current work progress.
    workInProgress = performUnitOfWork(workInProgress)
  }
}
Copy the code

React 16 how does React 16 fix the DOM rendering problem when breaking updates?

In React 16, Reconciler and Renderer no longer work alternately. When Scheduler hands it to Reconciler, the Reconciler marks the changing virtual DOM.

export const Placement = / * * / 0b0000000000010
export const Update = / * * / 0b0000000000100
export const PlacementAndUpdate = / * * / 0b0000000000110
export const Deletion = / * * / 0b0000000001000
Copy the code
  • PlacementRepresents an insert operation
  • PlacementAndUpdateRepresents the replacement operation
  • UpdateRepresents the update operation
  • DeletionRepresents the delete operation

The entire Scheduler and Reconciler’s work takes place in memory, so users do not see the DOM partially updated even if it is interrupted repeatedly. Only when all components are finished with the Reconciler will the Reconciler be uniformly handed over to the Renderer.

The Renderer Renderer

The Renderer synchronously performs DOM operations based on the Reconciler’s markings for the virtual DOM.

The lifecycle impact of the Fiber architecture

  1. Render phase: Pure and without side effects, may be suspended, terminated, or restarted by React.
  2. Pre-commit phase: DOM can be read.
  3. 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.

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.

I wrote a post about why some of the React lifecycle functions are going to be deprecated

This time, from the perspective of the Render stage of Firber mechanism, the three life cycles are all in the Render stage:

componentWillMount
componentWillUpdate
componentWillReceiveProps
Copy the code

Since the Render phase allows pauses, terminations, and restarts, it is possible to repeat the render phase’s life cycle, and is therefore one of the reasons for deprecating them.

Fiber update process

The virtual DOM update process is divided into two stages:

  • Render/Reconciliation: Uses the Diff algorithm to find all node changes, such as node additions, deletions, attribute changes, etc., to get node information that needs to be updated, corresponding to the earlier version of the Diff process.
  • Commit Phase (non-interruptible or synchronous) : The nodes to be updated are updated in batches, corresponding to the patch process of an earlier version.

Coordinate phase

During the coordination phase, Diff calculations are performed and a Fiber tree is generated.

This phase begins with performSyncWorkOnRoot or performConcurrentWorkOnRoot method call. This depends on whether the update is synchronous or asynchronous.

// performSyncWorkOnRoot calls this method
function workLoopSync() {
  while(workInProgress ! = =null) {
    performUnitOfWork(workInProgress)
  }
}

/ / performConcurrentWorkOnRoot will invoke this method
function workLoopConcurrent() {
  while(workInProgress ! = =null && !shouldYield()) {
    performUnitOfWork(workInProgress)
  }
}
Copy the code

The only difference is whether or not shouldYield is called. If the current browser frame has no time left, shouldYield stops the loop until the browser has free time to continue traversing.

WorkInProgress represents the currently created workInProgress Fiber.

The performUnitOfWork method triggers a call to beginWork, which in turn creates a new Fiber node. If the Fiber node created by beginWork is not empty, formUniofWork uses the new Fiber node to update the value of workInProgress in preparation for the next loop.

The beginWork is triggered by a loop call to performUnitOfWork, and new Fiber nodes are constantly created. When workInProgress is finally empty, there are no new nodes to create and the entire Fiber tree has been built.

We know that The Fiber Reconciler is reconstructed from the Stack Reconciler, achieving interruptible recursion through traversal, so the work of performUnitOfWork can be divided into two parts: “recursion” and “return.”

“Delivery phase”

First, the downward depth-first traversal starts from rootFiber. Call the beginWork method for each Fiber node traversed.

function beginWork(
  current: Fiber | null.// The Fiber node corresponding to the current component was the Fiber node when it was last updated
  workInProgress: Fiber, // The Fiber node corresponding to the current component
  renderExpirationTime: ExpirationTime // Priority is related
) :Fiber | null {
  / /... Omitting function body
}
Copy the code

This method creates sub-Fiber nodes from the incoming Fiber node and connects the two Fiber nodes.

The “homing” phase occurs when a leaf node (that is, a component that has no child components) is traversed.

“Return stage”

The completeWork is called in the “attribution” stage to handle the Fiber node.

CompleteWork will enter the creation and processing logic of different DOM nodes depending on the tag attribute of the workInProgress node.

There are three key actions within completeWork:

  • Create a DOM node (CreateInstance)
  • Insert the DOM node into the DOM tree (AppendAllChildren)
  • Set properties for the DOM node (FinalizeInitialChildren)

When a Fiber node completes completeWork, if it has a sibling Fiber node (i.e., fiber.sibling! == null), will enter the “recursive” phase of its sibling Fiber.

If there are no sibling fibers, the parent Fiber will enter the “attribution” phase.

The “recursion” and “return” phases are staggered until “return” reaches rootFiber. At this point, the coordination phase is over.

Commit phase

The main work of the COMMIT phase (that is, the Renderer’s workflow) is divided into three parts:

  • Before mutation stage, in which the DOM node has not been rendered to the interface, is triggered during the mutation stagegetSnapshotBeforeUpdate, will also be processeduseEffectHook-related scheduling logic.
  • The mutation stage, which is responsible for rendering DOM nodes. During rendering, the effectList is iterated over, performing different DOM operations depending on flags (EffectTags).
  • The Layout stage, which handles the finishing logic after DOM rendering is complete. Such as callcomponentDidMount/componentDidUpdate, the calluseLayoutEffectHook function callbacks, etc. In addition to this, it points the fiberRoot current pointer to the workInProgress Fiber tree.

Reference:

  • React
  • React Technology revealed


Open source projects I will be maintaining in the near future:

  • Use React + TypeScript + Dumi + Jest + Enzyme to develop UI component libraries
  • Next. Js Enterprise project scaffold template
  • If you feel good, welcome star, give me a little encouragement to continue writing ~