Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Why Fiber

Prior to Act15, if the page was complex, had a lot of elements, and had to refresh state frequently, the page would not flow smoothly and would drop frames. The reason is that because JS is single-threaded, a lot of synchronous operations block the UI rendering of the page. When React calls setState, React iterates through all the application nodes, calculates the differences that need to be updated, and then updates the UI. The whole process is in one step, can not be interrupted, so if there are too many elements will appear to stall.

What is the Fiber

There are usually two solutions to solve the “synchronous blocking” problem: asynchronous or sharding (task disassembly). React Uses sharding to deal with synchronization congestion. This is what we call the Fiber architecture.

Fiber is a refactoring of the React core algorithm, which essentially replaces the original Stack Reconciler with a Fiber Reconciler.

Fiber has five main goals:

  • Ability to slice interruptible tasks.
  • Ability to adjust priorities, reset and reuse tasks.
  • The ability to interleave parent and child elements to support layout in React.
  • Ability to return multiple elements in render().
  • Better support for error boundaries.

PS: React code base overview

Single list tree structure

Singly Linked List Tree Structure

Fiber’s data structure is characterized by its own single-linked list tree structure, allowing for fast insertion and deletion operations. Part of the source code for the type definition is as follows:

export type Fiber = { // Tag identifying the type of fiber. tag: WorkTag, // Unique identifier of this child. key: null | string, // The value of element.type which is used to preserve the identity during // reconciliation of this child. elementType:  any, // The resolved function/class/ associated with this fiber. type: any, // The local state associated with this fiber. stateNode: any, // Conceptual aliases // parent : Instance -> return The parent happens to be the same as the // return fiber since we've merged the fiber and instance. // Remaining fields belong to Fiber // The Fiber to return to after finishing processing this one. // This is effectively the parent, but there can be multiple parents (two) // so this is only the parent of the thing we're currently processing. // It is conceptually the same as the return address of a stack frame. return: Fiber | null, // Singly Linked List Tree Structure. child: Fiber | null, sibling: Fiber | null, index: number, ... // Singly linked list fast path to the next fiber with side-effects. nextEffect: Fiber | null, // The first and last fiber with side-effect within this subtree. This allows // us to reuse a slice of the linked list When we reuse the work done within / / this fiber. The storage (task execution status, of course) firstEffect: fiber | null, lastEffect: Fiber | null, lanes: Lanes, childLanes: Lanes, // This is a pooled version of a Fiber. Every fiber that gets updated will // eventually have a pair. There are cases when we can clean up pairs to save // memory if we need to. alternate: Fiber | null, ... }Copy the code

PS: details to check (https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js)

Fiber node constructor

The format of the Fiber node mainly includes

function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // Instance this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // Fiber 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; // Effects this.flags = NoFlags; this.subtreeFlags = NoFlags; this.deletions = null; this.lanes = NoLanes; this.childLanes = NoLanes; this.alternate = null; . }Copy the code

PS: details to check (https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiber.new.js)

The implementation process

The Fiber Reconciler is implemented in two phases.

1. render/reconciliation

Render/Reconciliation generates the fiber tree to get the differences that need to be updated, the process can be broken,

Double buffer pool technology is used here, Fiber only needs two versions of the tree at most. In this phase, in addition to generating the Fiber tree and building the workInProgress Tree during diFF, each Fiber node requires an alternate property (itself a Fiber structure). Alternate use current. Alternate when creating workInProgress.

The purpose of the workInProgress Tree is to reuse fiber.

With Fiber Reconciler as the working unit, the workInProgress Tree is constructed from the top down.

Specific process:

  1. If the current node does not need to be updated, clone the child node and jump to 5. Tag it if you want to update it
  2. Update the current node state (props, state, context, etc.)
  3. ShouldComponentUpdate (), false, jump to 5
  4. Call Render () to get the new child node and create fiber for the child node (the creation process will reuse the existing fiber as much as possible, the addition and deletion of the child node will also happen here)
  5. If no child fiber is generated, the unit of work ends, merge the Effect list to return, and use the sibling of the current node as the next unit of work. Otherwise, make child the next unit of work
  6. If there is no time left, wait until the next main thread is idle before starting the next unit of work; Otherwise, start right away
  7. If there is no next unit of work (back to the root of the workInProgress Tree), phase 1 ends and the pendingCommit state is entered

Ps: Reference (www.ayqy.net/blog/dive-i…)

The process of building the workInProgress Tree is the process of diff, scheduling a set of tasks through the requestIdleCallback, coming back after each task to see if there are any queue-cutters (more urgent), giving control of time back to the main thread after each set of tasks, Continue building the workInProgress Tree until the next requestIdleCallback callback

Window. RequestIdleCallback () will, in turn, calls the function in the browser free period, it can let developers performing background in the main event loop or low priority task, and not to delay trigger, such as animation and user interaction influence but the key event. Functions are generally executed in first-come-first-called order, unless the function reaches its timeout before the browser calls it

To implement the group execution process above, React has a Scheduler to assign tasks. There are six priorities for tasks:

  • Synchronous, as with the previous Stack Reconciler operation, is executed simultaneously
  • Task is executed before the next tick
  • Animation executes before the next frame
  • High, execute immediately in the near future
  • Low, it doesn’t matter if you delay it a little bit
  • Offscreen is executed at render time or scroll time next time

Synchronous first screen (first render) is used and requires as fast as possible, whether it blocks the UI thread or not. The animation is scheduled with a requestAnimationFrame so that the animation process starts immediately on the next frame; The last three are all performed by the requestIdleCallback callback; Offscreen refers to the currently hidden, off-screen (invisible) element

High-priority tasks (such as keyboard input) can interrupt low-priority tasks (such as Diff) to take effect more quickly.

If the current pointer is set to the workInProgress tree, the new Fiber Tree is created. If the current pointer is set to the workInProgress Tree, the old Fiber Tree is discarded.

Part of the source code is as follows:

// This is used to create an alternate fiber to do work on. export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { let workInProgress = current.alternate; if (workInProgress === null) { // We use a double **buffering pooling technique** because we know that we'll // only ever need at most two versions of a tree. We pool the "other" unused // node that we're free to reuse. This is lazily created to avoid allocating // extra objects for things that are never updated. It also allow us to // reclaim the extra  memory if needed. workInProgress = createFiber( current.tag, pendingProps, current.key, current.mode, ); workInProgress.elementType = current.elementType; workInProgress.type = current.type; workInProgress.stateNode = current.stateNode; // Fiber and workInProgress hold references to workinprogress. alternate = current; current.alternate = workInProgress; } else { workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type. workInProgress.type = current.type; // We already have an alternate. // Reset the effect tag. workInProgress.flags = NoFlags; // The effects are no longer valid. workInProgress.subtreeFlags = NoFlags; workInProgress.deletions = null; if (enableProfilerTimer) { // We intentionally reset, rather than copy, actualDuration & actualStartTime. // This prevents time from endlessly accumulating in new commits. // This has the downside of resetting values for different priority renders, // But works for yielding (the common case) and should support resuming. workInProgress.actualDuration = 0; workInProgress.actualStartTime = -1; } } // Reset all effects except static ones. // Static effects are not specific to a render. workInProgress.flags = current.flags & StaticMask; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; workInProgress.memoizedProps = current.memoizedProps; workInProgress.memoizedState = current.memoizedState; workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so // it cannot be shared with the current fiber. const currentDependencies = current.dependencies; workInProgress.dependencies = currentDependencies === null ? null : { lanes: currentDependencies.lanes, firstContext: currentDependencies.firstContext, }; // These will be overridden during the parent's reconciliation workInProgress.sibling = current.sibling; workInProgress.index = current.index; workInProgress.ref = current.ref; if (enableProfilerTimer) { workInProgress.selfBaseDuration = current.selfBaseDuration; workInProgress.treeBaseDuration = current.treeBaseDuration; } return workInProgress; }Copy the code

The life cycle that will be executed in this phase includes:

ComponentWillMount (obsolete) componentWillReceiveProps (obsolete) getDerivedStateFromProps shouldComponentUpdate ComponentWillUpdate (deprecated)Copy the code

2. commit

Commit Update the effect list in batches without interrupting the process.

The lifecycle executed in this phase:

componentDidMount
componentDidUpdate
componentWillUnmount
Copy the code

Fiber tree structure diagram

(Photo from Internet)

Q&A

How does Fiber break/breakpoint recovery?

Interrupt: Check the unit of work you are currently working on, save the current work (firstEffect, lastEffect), change the tag, close up quickly and open a requestIdleCallback for the next opportunity

Breakpoint recovery: The next time the unit of work is processed, see that the tag is the broken task and proceed with the unfinished part or redo

conclusion

If this article helped you, please like 👍 and follow ⭐️.

If there are any errors in this article, please correct them in the comments section 🙏🙏.

The attached:

  • Lin Clark — A Cartoon Intro to Fiber — React Conf 2017
  • ReactFiber
  • ReactInternalTypes
  • React code base overview
  • render/reconciliation
  • React Fiber works