Learning the React Framework - 004 Rendering Slicing and Underlying Fiber
How Does React Render Components?
Whenever you change the state in a component, React can immediately display the changes in the UI. Through our learning these past few days, we understand that React renders components to the page through Render and re-renders the page by observing state updates. But we don't clearly understand what optimizations React does for developers in rendering. Next, let's take a shallow dive into React's efforts in Render, and after Render is triggered, what methods does React use to compare differences between states and what is its underlying implementation based on?
State Snapshots
React creates snapshots of components, capturing everything React needs to update the view at a specific moment: state, event handlers, and UI descriptions.
Rendering Mode Before React 16
Before React 16, updates needed to go through the reconciler (determining which components need updates, interruptible) then be scheduled to the renderer. After execution, synchronous rendering would occur. When pages were complex with nested component structures, changing one value required waiting a relatively long time for updates. Moreover, changing parent component state would cause all child components underneath to re-render together.
Rendering Mode After React 16
Through the fiber structure, the synchronous rendering approach was changed to asynchronous rendering and task slicing technology, converting each component from virtual dom tree => fiber tree, implementing a structure that can be updated asynchronously. This allows the rendering process to be interrupted, paused, and resumed, thereby better controlling rendering priority, improving application responsiveness, and avoiding waiting time for rendering and JavaScript thread occupation issues.
Fiber
It can be viewed as a data structure that saves component node attributes, types, and DOM, and forms and connects the Fiber tree through pointers to child, sibling, and return. This data structure divides the rendering process into interruptible units to support incremental rendering and better user interaction, distinguishing different levels and rendering priorities of component trees.
Works with the requestIdleCallback API call during browser idle time to implement task splitting, interruption, and resumption.
Will be converted to
Each React element has a corresponding fiber node. Unlike React elements, fibers are not recreated on every render. These are mutable data structures that hold component state and DOM.
After the first render, React gets a fiber tree that reflects the application's state used to render the UI. This tree is often called the current tree (Current Fiber tree).
When React starts processing updates, it builds a so-called workInProgress tree that reflects the future state to be flushed to the page.
Once updates are processed and all related work is completed, React will have the workInProgress tree ready to flush to the page. Once this workInProgress tree is rendered to the page, it becomes the current tree.
We can understand that React stores two tree-like reference tables that compare with each other to analyze which nodes and states need to change, triggering re-rendering. This is also called double buffering technique.
React always updates the DOM all at once—it doesn't show partial results.
Side-effects
Think of components in React as functions that use state and props to compute UI representation. Any function that triggers computation, such as changing the DOM or calling lifecycle methods, should be considered a side effect, or simply an effect.
Therefore, fiber nodes are a convenient mechanism for tracking side-effects in addition to updates. Each fiber node can have effects associated with it. They are indicated in the effectTag property.
Effect List
When component state on the page updates, we need to record which components have changed in their lifecycle or functions, triggering side effects. The Effect List uses a traceable linear list to record these processes, executed in order from child to parent (deep to shallow recording), marked with different tags (firstEffect, lastEffect, nextEffect) in fiberNode to mark Effect order, and finally passed to Root to construct the list.
Render and Commit Phases
React executes Virtual Dom conversion to Fiber tree, compares node differences, executes side effects, and finally displays and loads onto the page in two phases:
- Render (can execute asynchronously, interruptible) => Mainly creates Fiber Tree and generates EffectList.
Most fibers in React elements will be reused and updated rather than regenerated, to reduce memory consumption.
- Commit (executes synchronously, cannot be interrupted) => Traverses the effectList generated by Render, observes props changes and state saved in Fiber nodes on the effectList. Finally performs Dom operations and lifecycle execution, executes operations in hooks, or destroys unused functions.
This phase will execute single-threaded, and users will see screen changes, so it cannot be paused.
Work loop
All fiber node work searching is handled in the work loop. nextUnitOfWork holds a reference to the fiber node from the workInProgress tree. In this while loop, nodes will be continuously recursed to find if there's unfinished work. Only after all work starting from child nodes is completed will parent node and backtracking work be completed.
completeUnitOfWork and completeUnitOfWork are mainly used for iteration purposes, while the main activity occurs in beginWork and completeWork functions.
Implementation: