React Fiber Reconciler

As of version 16, React has significantly enhanced its rendering mechanism by completely rewriting its core reconciliation module. In this post, we will explore the internal changes that React has made, their purpose in addressing specific problems React has had, and their implementation details.

Reconciliation

Reconciliation is a process that occurs when you invoke ReactDOM.render() or React.setState(). During setState(), React recursively traverses the component tree and invokes the render function for each component to identify the underlying DOM element it produces. Subsequently, React determines the differences between the newly generated tree and the previously rendered tree.

Rendering

Once React has identified the necessary updates through the reconciliation process, it applies these changes to the current component tree to synchronize the UI with the updated state. This process is known as rendering. In React, there are distinct modules for reconciliation and rendering, which allows for sharing of core reconciliation modules across different React libraries like React and React Native, while using different renderers as needed.

📚Stack Reconciler

React's reconciler before version 16 was called "stack reconciler."

  • Whenever there is an update, the stack reconciler traverses the component tree recursively in a Depth-First manner and re-renders the entire tree.
  • The stack reconciler combines reconciliation and rendering into a single call stack frame, which gives the reconciler the name as such.
  • In another word, the stack reconciler works synchronously.

❌ In large, complex applications with frequent updates, the stack frame generated by the stack reconciler can block the main thread. As a result, he browser is unable to process user inputs and other tasks until the rendering process completes. The UI becomes unresponsive and can negatively impact overall user experience.

❌ In certain scenarios, it may not be essential to apply all updates immediately. For instance, users may not be bothered by waiting for data fetching for a few seconds, but they are likely to perceive less than a second of unresponsiveness to user interactions as janky.

For instance, users may not be bothered by waiting for data fetching for a few seconds but may find less than a second of unresponsiveness to user interactions janky. However, React was unable to schedule or prioritize tasks, since the reconciliation and rendering process couldn't be interrupted, nor broken into smaller tasks.  

❌ Dropped frame; For the human eyes to perceive it as natural and smooth, the screen's frame has to be refreshed every 16ms. When an update in React application takes too long, the browser's FPS(Frames Per Second) rate drops, resulting in choppy animation. (For more on web animation: 🔗 Browser Animation - setInterval vs. requestAnimationFrame)

  • Also, the node in the tree was immutable.

❌ It means that the entire tree had to be re-created to sync with the app's updated state.

🪡Fiber Reconciler

To address the challenges posed by the stack reconciler, the React team rewrote it using the fiber reconciler. As per Wikipedia, "a fiber is a lightweight thread of execution in computer science." The term "fiber" emphasizes the new reconciler's ability to break down a "stack" frame into lightweight, micro steps. In the following sections, we'll explore the changes introduced by the fiber reconciler and its various advantages.

// 여기서부터 //

  • To the fiber reconciler, a unit of work is each node in the tree, not the entire tree. Each of these nodes represents a virtual stack frame, which differs from a regular frame in a call stack. By dividing a call stack frame into multiple virtual frames, it becomes possible to pause and resume work after processing each fiber node.
  • In other words, the fiber reconciler works concurrently.
ℹ️
parallel vs. concurrent
When tasks are processed in parallel, multiple cores handle different tasks simultaneously. This means that multiple things are happening at the exact same instant. However, when using a single core, it is not possible to process multiple tasks in parallel. Instead, the core can break a higher-order task into smaller microtasks, switch context between these microtasks, and handle them within the same time frame. In this case, the works are being done concurrently. By doing so, the core can give the impression that two higher-order tasks are being processed in parallel.
  • The fiber reconciler continually monitors the main thread to see if there are tasks with higher priority, such as user input or animation.

🟢 As a consequence, React state update no longer blocks the call stack as it used to with stack reconciler. The user experience is enhanced.

  • Also, the fiber node is mutable

🟢 Hence, React can reuse previously completed work. Instead of recreating every node, it simply clones the updated nodes and makes changes to them.

  • Fiber nodes form a singly-linked list. Upon pausing reconciliation, React can easily resume the work by finding the next fiber node to process, pointed by the previous fiber node.
  • Fiber nodes are organized in a singly-linked list. Upon pausing reconciliation, React can easily resume work by looking at the next fiber node to process, which is pointed to by the fiber node that was most recently handled.

Anatomy

In React, Fiber can mean either a fiber node or the fiber reconciler and its algorithm.

Fiber Node

Each fiber in the React application corresponds to an element in the virtual DOM tree and contains the state, props, and underlying DOM element of the component it renders. To access a fiber node, you can run a React application and look for a key that begins with "__reactFiber".

Imagine there's an app with a ul tag with 3 li tags as its children. The following is a simplified singly-linked list created by the fiber reconciler.

Let's take a look at some of the fundamental attributes of a fiber node object.

alternate: Each React component has two corresponding fiber nodes–the flushed fiber node and the workInProgress fiber node. While the flushed fiber node represents the current state of the app, the workInProgress fiber node is part of the node tree that is currently creating to synchronize with the state update. A flushed node and a workInProgress  node are each other's alternate.

child: The child of a fiber node points to the next fiber node to be worked on. In the above example, li #1 is the child of ul.

sibling: In the above construct, 3 li elements are siblings.

return: return represents the logical return that sends the reconciler back to the parent fiber node after processing all its children nodes.

memoizedProps is the props passed to the node in the last tick.

pendingProps is the props being passed to the component in the current tick.

When the incoming pendingProps is equal to the memoizedProps, the fiber’s previous output can be reused, preventing unnecessary re-computation.

firstEffect, lastEffect, nextEffect: During reconciliation, the fiber reconciler marks the difference from the previous tree as 'effects.' The fiber reconciler can efficiently navigate through these effects, starting from the firstEffect and continuing to the nextEffects, until it meets lastEffect.

Fiber Algorithm

Fiber's reconciliation consists of two phases–the render phase and the commit phase.

Render Phase

During the render phase, the reconciler creates a new fiber tree that contains information about the updates to the application.

  1. It begins by cloning the root fiber node that the current attribute of the app's root element points to. From there, it continues to create a new tree of fiber nodes, which is called the workInProgress tree and serves as an alternate to the current tree.
  2. The reconciler marks a node if there is an update to it
    2 - 1. If the node has child, it repeats this process for its children
    2 - 2. If the node has sibling, it repeats the process for its siblings as well.

The work of the render phase is concurrent.

Commit Phase

It is during the commit phase that the changes identified during the render phase are reflected to the DOM.

  1. First, the reconciler gathers the effects that were marked in the workInProgress tree created during the render phrase and flushes those changes to the real DOM tree.
  2. Then, the workInProgress tree is set as the current of the root element. As a result, the workInProgress tree becomes the new flushed tree and the previously flushed tree becomes the workInProgress tree. By doing so, React manages memory efficiently by reusing the previous flushed tree to make the workInProgress tree in the next tick.

It is important to note that the commit phase is synchronous and cannot be stopped or resumed. Doing so would result in the UI updating gradually, which could make it appear less smooth.

References

Lee, Yeji, "React Deep Dive – Fiber." 2022
Karthik Kalyanaraman, "A Deep dive into React Fiber." 2022
Clark, Lin. (2017, ) A Cartoon Intro to Fiber [video file]. Retrieved from https://www.youtube.com/watch?v=ZCuYPiUIONs&t=865s
react GitHub repository
Simpson, Kyle. (2016, March 29). Rethinking Asynchronous JavaScript [video file]. Retrieved from https://frontendmasters.com/courses/rethinking-async-js/
Wikipedia

Subscribe to go-kahlo-lee

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe