React Fiber introduction — The algorithm behind React
In this article, we’ll learn about React Fiber — the core algorithm behind React. React Fiber is a new coordination algorithm in React 16. You’ve probably heard of virtualDOM in React 15, which is the old coordination algorithm (also known as the stack coordinator because it uses a stack internally). Different renderers, such as DOM, Native, and Android views, all share the same coordinator, so calling it virtualDOM can be confusing.
Let’s take a quick look at React Fiber.
introduce
React Fiber is a fully backward compatible version for rewrites on the coordinator. React’s new reconciliation algorithm is called Fiber Reconciler. The name comes from Fiber, which is often used to refer to nodes in a DOM tree. We’ll cover Fiber in more detail in a later section.
The main goal of the Fiber Coordinator is incremental rendering, better and smoother rendering of UI animations and gestures, and responsiveness of user interactions. The coordinator also allows you to divide work into blocks and render work into frames. It also increases the ability to define priorities for each unit of work, as well as the ability to pause, reuse, and suspend work.
Some of React’s other features include returning multiple elements from a render function, support for better error handling (we can use the componentDidCatch method for clearer error messages), and portals.
React returns to the main thread multiple times when calculating new render updates. Thus, high-priority work can skip low-priority work. React internally defines a priority for each update.
Before getting into the technical details, I recommend that you learn the following terminology that will help you understand React Fiber.
A prerequisite for
coordinate
As explained in the official React documentation, reconciliation is an algorithm of two DOM tree diff. React creates a tree of nodes when the user interface is first rendered. Each individual node represents the React element. It creates a virtual tree (called virtualDOM) that is a copy of the rendered DOM tree. After any updates from the user interface, it recursively compares each tree node of the two trees. The cumulative changes are then passed to the renderer.
scheduling
As the official React documentation explains, let’s say we have some low-priority work (such as large computational functions or rendering of recently acquired elements), and some high-priority work (such as animation). There should be an option to prioritize high-priority work over low-priority work. In the old stack coordinator implementation, recursively traversing and calling the rendering method of the entire updated tree occurred in a single process, which could result in frame loss.
Scheduling can be time-based or priority-based. Updates should be scheduled according to the deadline, and high-priority work should be prioritized over low-priority work.
RequestIdleCallback (requestIdleCallback)
RequestAnimationFrame schedules the higher-priority function to be called before the next animation frame. Similarly, requestIdleCallback schedules low-priority or non-essential functions to be called at idle time at the end of the frame.
requestIdleCallback(lowPriorityWork);
Copy the code
This shows the use of requestIdleCallback. LowPriorityWork is a callback function that will be called during idle time at the end of the frame.
function lowPriorityWork(deadline) {
while (deadline.timeRemaining() > 0 && workList.length > 0)
performUnitOfWork();
if (workList.length > 0) requestIdleCallback(lowPriorityWork);
}
Copy the code
When this callback function is called, it gets the Deadline object. As you can see in the clip above, the timeRemaining function returns the most recent free timeRemaining. If this time is greater than zero, we can do the necessary work. If the work is not finished, we can rearrange the work on the last line of the next frame.
So now we can move on to the Fiber object itself and see how React Fiber works.
The structure of the Fiber
A fiber (lowercase ‘f’) is a simple JavaScript object. It represents the React element or a node in the DOM tree. It’s a work unit. In contrast, Fiber is the React Fiber coordinator.
This example shows a simple React component rendered in a root div.
function App() {
return (
<div className="wrapper">
<div className="list">
<div className="list_item">List item A</div>
<div className="list_item">List item B</div>
</div>
<div className="section">
<button>Add</button>
<span>No. of items: 2</span>
</div>
</div>
);
}
ReactDOM.render(<App />.document.getElementById("root"));
Copy the code
This is a simple component that displays a list item for the data we get from the component state. (I replaced.map and iterating over the data with two list items, just to make the example easier.) There is also a button and SPAN, which shows the number of list items.
As mentioned earlier, fiber represents the React element. On the first render, React looks through each React element and creates an fibers tree. We’ll see how it creates this tree in a later section.
It creates a fiber for each individual React element, as in the example above. It will create a fiber for the div, such as W, whose class is Wrapper. Then create L Fiber for the div with the List class, and so on. Let’s name fiber LA and LB for the two list items.
In a later section, we’ll see how it iterates and the final structure of the tree. Although we call it a tree, React Fiber creates a linked list of nodes where each node is a Fiber. And there is a relationship between father, son and sibling. React uses a return key to point to the parent node, and any child fiber should return to that node after it has finished its work. So, in the above example, the return of LA is L, while the sibling is LB.
So, what does this Fiber object actually look like?
Here’s how the React codebase defines the fiber type. I removed some extra props and kept some comments to understand what the properties meant. You can find the detailed structure in React Codebase.
export type Fiber = {
// Recognize the label of type fiber.
tag: TypeOfWork,
// Unique identifier of child.
key: null | string,
// The element value. Type that is used to preserve identity while coordinating the child.
elementType: any,
// The resolved function/class associated with this fiber.
type: any,
// Current state related to this fiber.
stateNode: any,
// fiber remaining fields
// Return to fiber after handling this problem.
// This is actually parent.
It is conceptually the same as the return address of a stack frame.
return: Fiber | null.// Single list tree structure.
child: Fiber | null.sibling: Fiber | null.index: number,
// The last reference used to connect the node.
ref:
| null
| (((handle: mixed) = > void) & { _stringRef: ?string, ... })
| RefObject,
// Enter the data of this fiber. The Arguments and Props.
pendingProps: any, // This type will become more specific once we overload the label.
memoizedProps: any, // The item used to create output.
// a queue for status updates and callbacks.
updateQueue: mixed,
// The state used to create output
memoizedState: any,
mode: TypeOfMode,
// Effect
effectTag: SideEffectTag,
subtreeTag: SubtreeTag,
deletions: Array<Fiber> | null.// Single linked list fast to the next fiber side effect.
nextEffect: Fiber | null.// In this subtree, the first and last fiber have side effects.
// This allows us to reuse a segment of the linked list while reusing the work done in this fiber.
firstEffect: Fiber | null.lastEffect: Fiber | null.// This is an integrated version of Fiber. Each updated fiber ends up in a pair.
// In some cases, we can clean up pairs of fibers if necessary to save memory.
alternate: Fiber | null};Copy the code
How does React Fiber work?
Next, we’ll see how React Fiber creates the linked list tree and what it does when there are updates.
Before we do that, let’s explain what the Current Tree and workInProgress Tree are and how to do tree traversal.
The tree that is currently being refreshed to render the user interface, called current, is used to render the current user interface. Whenever there is an update, Fiber creates a workInProgress tree, which is created from the update data in the React element. React performs work on the workInProgress tree and uses the updated tree for the next rendering. Once the workInProgress tree is rendered to the user interface, it becomes the Current tree.
The traversal of the Fiber tree happens like this.
- Start: Fiber traverses from the React element at the top and creates a Fiber node for it.
- Child node: Then, it goes to the child element and creates a Fiber node for that element. This continues until you reach the leaf element.
- Sibling node: Now, it checks to see if there are sibling node elements. If so, it traverses the sibling node elements and then the sibling leaf elements.
- Returns: If there are no siblings, then it returns to the parent node.
Each fiber has a child attribute (null if none), sibling, and parent node attributes (as you saw in the structure of fiber in the previous section). These are Pointers in Fiber that work as a linked list.
For the same example, let’s first name fiber for the React element.
function App() {
// App
return (
<div className="wrapper">
{" "}
// W<div className="list">
{" "}
// L<div className="list_item">List item A</div> // LA
<div className="list_item">List item B</div> // LB
</div>
<div className="section">
{" "}
// S<button>Add</button> // SB
<span>No. of items: 2</span> // SS
</div>
</div>
);
}
ReactDOM.render(<App />.document.getElementById("root")); // HostRoot
Copy the code
First, we’ll take a quick look at the mount phase when creating the tree, and then we’ll look at the detailed logic after the tree is updated.
The initial rendering
The App component is rendered in a root div with an ID of root.
Before traversing further, React Fiber creates a root Fiber. Each Fiber tree has a root node. In our case, it is HostRoot. If we import multiple React applications into the DOM, we can have multiple root nodes.
There will not be any trees until the first rendering. React Fiber traverses the output of each component’s render function and creates a Fiber node in the tree for each React element. It will React by createFiberFromTypeAndProps elements into a fiber. The React element can be a class component or a host component, such as a div or span. For the class component, it creates an instance, and for the host component, it gets the data and props from the React element.
So, as shown in the example, it creates a Fiber App. Further down, it creates a fiber, W, then it goes to a child div and creates a fiber L, and so on, it creates a fiber, LA, and LB for its children. Fiber, LA, will have returned (in this case can also be called parent) Fiber as L, and siblings as LB.
So this is what the final Fiber tree looks like.
This is how tree nodes are joined using child nodes, sibling nodes, and return Pointers.
Update the stage
Now, let’s talk about the second case, such as updates due to setState.
So, at this point, Fiber already has the current tree. For each update, it creates a workInProgress tree. It starts at the root fiber and traverses the tree until it reaches the leaf node. Unlike the initial render phase, it does not create a new fiber for each React element. It simply uses preexisting Fiber for the React element and merges the new data and props from the update element during the update phase.
Earlier, in React 15, the stack coordinator was synchronized. So, an update recursively traverses the entire tree and makes a copy of the tree. Assuming that in between, there is no opportunity to abort or pause the first update and perform the second if there is another update that has a higher priority than it.
React Fiber divides updates into units of work. It can assign priority to each unit of work and has the ability to pause, reuse, or abort units of work when no longer needed. React Fiber divides work into multiple work units, known as fibers. It arranges work across multiple frameworks and uses deadlines from requestIdleCallback. Each update has a definition of its priority, such as an animation, or a list of items rendered from data that the user input takes precedence over. Fiber uses requestAnimationFrame for higher-priority updates and requestIdleCallback for lower-priority updates. Thus, when scheduling work, Fiber checks the priority of the current update and the deadline (the free time after the frame ends).
Fiber can schedule multiple units of work after a frame if the priority is higher than the work to be processed, or if there is no deadline or deadline has not been reached. The next set of units of work is carried over to more frames. This is what makes it possible for Fiber to pause, reuse, and abort units of work.
So, let’s look at what actually happens at the scheduled job. There are two stages to get the job done. Render and commit.
Rendering phase
The actual tree traversal and deadline use occurs in this phase. This is Fiber’s internal logic, so changes made to the Fiber tree at this stage are not visible to the user. As a result, Fiber can pause, suspend, or share work with multiple frameworks.
We can call this stage the coordination stage. Fiber traverses from the root of the fiber tree, processing each fiber. Each unit of work calls the workLoop function to perform the work. We can divide the work into two steps. Begin and complete.
At the start of
If you find the workLoop function in the React code base, it will call performUnitOfWork, which takes nextUnitOfWork as an argument, and it’s just a unit of work that will be executed. The beginWork function is called inside the performUnitOfWork function. This is where the actual work happens on Fiber, whereas performUnitOfWork is just where the iteration happens.
In the beginWork function, if Fiber doesn’t have any work to do, it jumps out of Fiber instead of entering the start phase. This is why, when traversing the tree, Fiber skips the processed fiber and goes straight to the fiber that needs to be processed. If you see the large code block for the beginWork function, we’ll find a switch block that calls the corresponding Fiber update function based on the Fiber label. Just like updateHostComponent hosts the component. These functions update fiber.
The beginWork function returns subfiber if there is one, or null if there is none. The function performUnitOfWork iterates over and calls subfiber until the leaf arrives. In the case of leaf nodes, beginWork returns null because there are no children, and the performUnitOfWork function calls the completeUnitOfWork function. Now let’s look at the completion phase.
Perfect stage
The completeUnitOfWork function completes the work of the current unit by calling a completeWork function. If there is one, the completeUnitOfWork returns a sibling fiber to execute the next unit of work, or completes the return(parent) fiber if there is no work. This will continue until the return value is empty, that is, until it reaches the root node. Like beginWork, completeWork is a function that actually does the work, and completeUnitOfWork is used for iteration.
The render phase results in a list of effects (side effects). These effects are like inserting, updating, or deleting a node of a host component, or calling a lifecycle method of a node of a class component. These fibers are labeled with their respective effect labels.
After the rendering phase, Fiber will be ready to commit the update.
The commit phase
This is a phase where the finished work will be used to render it in the user interface. Because the results at this stage are visible to the user, they cannot be partitioned. This phase is a synchronous phase.
At the beginning of this phase, Fiber has either the Current tree, finishedWork, or the workInProgress tree and effect list established during the render phase that has already been rendered on the UI.
The effect list is fiber’s linked list, and it has side effects. So, it is a subset of the nodes of the workInProgress tree in the render phase, and it has side effects (updates). The nodes in the Effect list are linked using the nextEffect pointer.
The function called at this stage is completeRoot.
Here, the workInProgress tree becomes the Current tree because it is used to render the UI. The actual DOM updates, such as inserts, updates, and deletions, as well as calls to lifecycle methods or updating corresponding references, occur on nodes in the Effect list.
This is how the Fiber coordinator works.
conclusion
This is where the React Fiber coordinator makes it possible to divide work into units of work. It sets the priority of each work and makes it possible to pause, reuse, and abort units of work. In fiber trees, individual nodes keep track, which is needed to make this possible. Each fiber is a linked list of nodes connected by children, siblings, and return references.
Here’s a list of documented resources to find out more about React Fiber.
Related articles
-
Build dynamic forms in React using Formik — faster and better
-
Write cleaner, more efficient code using Hooks and functional programming