React Fiber, the heart of React's concurrent rendering, enables the framework to break down tasks into smaller units and prioritize more important tasks, allowing for smoother and more responsive user interfaces. When paired with Suspense, it allows React to "suspend" rendering, displaying a fallback
UI while waiting for tasks like data fetching or computations to finish.
A Fiber is a JavaScript object that represents a unit of work in React. It holds important information about a component during the rendering process:
All the code in that article is pseudo. You can find real files links in the end of the article
{
tag, // The type of fiber (e.g., HostComponent, FunctionComponent)
stateNode, // The actual DOM node or instance for class components
memoizedProps, // Props used during the last render
memoizedState, // State used during the last render
return, // Parent fiber
sibling, // Next sibling fiber
child, // First child fiber
alternate, // Link to the previous fiber (for reconciling updates)
}
The fiber tree allows React to efficiently traverse the component tree, perform rendering work, and track updates, state, and DOM mutations.
Let's build a simple app consisting of Header, Content, and Footer components. To highlight how Fiber works, we’ll make Header perform a heavy computation.
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Header />
<Content />
<Footer />
</Suspense>
);
}
function Header() {
// Simulating heavy computation
for (let i = 0; i < 1e9; i++) {}
return <h1>Header with heavy computation</h1>;
}
function Content() {
return <p>This is the content area</p>;
}
function Footer() {
return <footer>Footer content</footer>;
}
How it starts? The render
function is responsible for starting the initial rendering process in React. It takes a root component (like App
) and creates the initial fiber tree for React to work on:
function render(element, container) {
const rootFiber = {
tag: HostRoot, // Root fiber tag
stateNode: container, // Reference to the DOM container
child: null, // Initial child will be added here
};
// Create a new work-in-progress fiber for our App component
const appFiber = createFiberFromElement(element);
rootFiber.child = appFiber;
// Start the rendering process
scheduleWork(rootFiber);
}
Then work scheduling take into an action. scheduleWork
is responsible for starting the work loop and processing the fiber tree. It determines when to start processing work, often using workLoop
.
function scheduleWork(rootFiber) {
nextUnitOfWork = rootFiber; // Set the root fiber as the starting point
requestIdleCallback(workLoop); // Begin processing fibers when the browser is idle
}
Here, the root fiber is set as the nextUnitOfWork
, and React schedules the workLoop
function to process it. React uses requestIdleCallback
to wait until the browser is idle before starting the work, which is essential for non-blocking rendering.
As mentioned earlier, workLoop
and performUnitOfWork
handle the actual processing of fibers. This is Reconciliation Phase.
workLoop
processes each fiber by calling performUnitOfWork
until all tasks are complete or the browser needs to prioritize other tasks.
function workLoop() {
while (nextUnitOfWork && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // Perform work for the next fiber
}
if (!nextUnitOfWork) {
// Commit all completed work to the DOM
commitRoot();
} else {
// Yield control and schedule the next chunk of work
requestIdleCallback(workLoop);
}
}
performUnitOfWork
processes individual fibers, checking for Suspense
boundaries, starting rendering tasks, and managing updates.
function performUnitOfWork(fiber) {
const next = beginWork(fiber);
// If there's more work to do, return the next unit
if (next) {
return next;
}
// Otherwise, complete the work and move to the next sibling or parent
let sibling = fiber.sibling;
if (sibling) {
return sibling;
}
return fiber.return;
}
function beginWork(fiber) {
if (fiber.tag === SuspenseComponent) {
if (fiber.suspenseState === Suspended) {
// Render fallback UI if data is not ready
return fiber.fallback;
} else if (fiber.suspenseState === Resolved) {
// Render actual content once data resolves
return fiber.child;
}
}
// Continue rendering for other fibers
}
After completing all the units of work, commitRoot
is responsible for committing the fiber tree to the DOM, including handling Suspense fallback UI. This is Commit Phase.
function commitRoot() {
let fiber = rootFiber;
while (fiber) {
if (fiber.tag === SuspenseComponent && fiber.suspenseState === Suspended) {
// Commit fallback UI if Suspense is active
updateDOM(fiber.stateNode);
} else {
// Commit normal content
updateDOM(fiber.memoizedProps);
}
// If the fiber has a passive effect (i.e., useEffect), schedule it
if (fiber.effectTag === Passive) {
// Function to schedule the useEffect hooks to run after the DOM updates and browser paint
scheduleEffectHooks(fiber);
}
fiber = fiber.next; // Move to the next fiber in the tree
}
}
During the commit phase, React invokes
scheduleEffectHooks
for any fiber that has side effects (likeuseEffect
). These hooks are deferred until after the DOM updates and browser paints to avoid blocking the rendering process. This allows React to perform non-blocking updates while ensuring that side-effects are executed in a timely manner once the UI is stable.
In this article, we explored the inner workings of React's Fiber architecture and how Suspense interacts with it to enhance the rendering process. We learned how React breaks down rendering tasks into smaller units, prioritizes important tasks, and defers less critical updates to keep the user interface smooth and responsive.
We gained a clear understanding of the fiber tree structure, the work scheduling process, and how reconciliation and commit phases work to update the DOM efficiently. We also explored how Suspense integrates within this architecture, displaying fallback content while awaiting data or heavy computations.
If you have any feedback or thoughts on the article—whether parts are unclear or could be improved—I’d be glad to hear them. Your insights will help refine and enhance the content for everyone.
Sources and original implementations:
Top comments (0)