Understanding React Fiber Architecture: Reconciliation, Scheduling, and Commit Phases
React Fiber replaces the old Stack Reconciler with a linked‑list of Fiber nodes that enable incremental, pause‑and‑resume rendering, priority scheduling, and a three‑step commit phase, allowing smoother asynchronous updates, better performance, and the foundation for Concurrent Mode and Suspense.
Preface
React v16.13 introduced the experimental Concurrent Mode and the new Suspense mechanism, which naturally solves long‑standing asynchronous side‑effect problems. Combined with Hooks (v16.8) and the Fiber architecture (v16.0), React provides a much smoother developer and user experience. This article explores the inner workings of React Fiber based on the v16.8.6 source code.
Stack Reconciler vs. Fiber Reconciler
Stack Reconciler was the coordination algorithm used in React v15 and earlier. Starting with v16, React Fiber rewrote the Stack Reconciler with a virtual stack‑frame model, allowing the traversal to be paused, aborted, or resumed. The original Stack Reconciler performed synchronous DOM updates, which could cause janky animations and poor UX. Fiber replaces the built‑in call stack with a linked‑list of Fiber nodes, each representing a unit of work.
Basic Concepts
Work
Any computation that must be performed during reconciliation—state updates, prop updates, ref updates, etc.—is collectively called work .
Fiber Object
Each React element corresponds to a Fiber object. A fiber stores component‑specific data and links to other fibers via child , sibling , and return pointers, forming a tree‑like linked list.
Fiber = {
// tag identifies the type of fiber (see WorkTag)
tag: WorkTag,
// parent fiber
return: Fiber | null,
// first child fiber
child: Fiber | null,
// next sibling fiber
sibling: Fiber | null,
// props set at the beginning of work
pendingProps: any,
// props after work finishes
memoizedProps: any,
// current state
memoizedState: any,
// effect type (see EffectTag)
effectTag: SideEffectTag,
// pointer to the next effect in the list
nextEffect: Fiber | null,
// first effect in the list
firstEffect: Fiber | null,
// last effect in the list
lastEffect: Fiber | null,
// expiration time used for priority ordering
expirationTime: ExpirationTime,
};WorkTag
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // before we know if it is function or class
export const HostRoot = 3; // root of a host tree
export const HostPortal = 4;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedSuspenseComponent = 18;
export const EventComponent = 19;
export const EventTarget = 20;
export const SuspenseListComponent = 21;EffectTag
export const NoEffect = 0b000000000000;
export const PerformedWork = 0b000000000001;
export const Placement = 0b000000000010;
export const Update = 0b000000000100;
export const PlacementAndUpdate = 0b000000000110;
export const Deletion = 0b000000001000;
export const ContentReset = 0b000000010000;
export const Callback = 0b000000100000;
export const DidCapture = 0b000001000000;
export const Ref = 0b000010000000;
export const Snapshot = 0b000100000000;
export const Passive = 0b001000000000;
export const LifecycleEffectMask = 0b001110100100;
export const HostEffectMask = 0b001111111111;
export const Incomplete = 0b010000000000;
export const ShouldCapture = 0b100000000000;Reconciliation and Scheduling
Reconciliation uses a diff algorithm to compare the virtual DOM and determine which parts need to change.
Scheduling decides when a piece of work should be executed, based on priority derived from expirationTime .
Render Phase
The render phase builds a new workInProgress tree. It can be paused and resumed because each Fiber stores its own context.
enqueueSetState
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};Priority Constants
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;renderRoot
function renderRoot(root, expirationTime, isSync) | null {
do {
// highest priority – sync branch
if (isSync) {
workLoopSync();
} else {
workLoop();
}
} while (true);
}
function workLoop() {
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}performUnitOfWork
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
let next;
next = beginWork(current, unitOfWork, renderExpirationTime);
if (next === null) {
next = completeUnitOfWork(unitOfWork);
}
return next;
}completeUnitOfWork
function completeUnitOfWork(unitOfWork) {
// depth‑first search
workInProgress = unitOfWork;
do {
const current = workInProgress.alternate;
const returnFiber = workInProgress.return;
// build effect‑list part
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
const siblingFiber = workInProgress.sibling;
if (siblingFiber !== null) {
return siblingFiber;
}
workInProgress = returnFiber;
} while (workInProgress !== null);
}Commit Phase
The commit phase applies the effect list to the real DOM and runs lifecycle methods. It consists of three sub‑phases: before‑mutation, mutation, and layout.
commitRootImpl
function commitRootImpl(root) {
if (firstEffect !== null) {
// before‑mutation phase
do {
try {
commitBeforeMutationEffects();
} catch (error) {
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
// mutation phase
nextEffect = firstEffect;
do {
try {
commitMutationEffects();
} catch (error) {
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
// replace work‑in‑progress with current tree
root.current = finishedWork;
// layout phase
nextEffect = firstEffect;
do {
try {
commitLayoutEffects(root, expirationTime);
} catch (error) {
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} while (nextEffect !== null);
nextEffect = null;
} else {
// No effects.
root.current = finishedWork;
}
}commitBeforeMutationEffects (ClassComponent example)
function commitBeforeMutationLifeCycles(current, finishedWork) {
switch (finishedWork.tag) {
case ClassComponent: {
if (finishedWork.effectTag & Snapshot) {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
return;
}
// other tags omitted for brevity
}
}commitMutationEffects
function commitMutationEffects() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
let primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement:
// ...
break;
case PlacementAndUpdate:
// ...
break;
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion:
commitDeletion(nextEffect);
break;
}
}
}commitLayoutEffects (ClassComponent example)
function commitLifeCycles(finishedRoot, current, finishedWork, committedExpirationTime) {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
instance.componentDidMount();
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
);
}
}
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
commitUpdateQueue(
finishedWork,
updateQueue,
instance,
committedExpirationTime,
);
}
return;
}
// other tags omitted for brevity
}
}Extensions
Additional topics include the call‑graph of Fiber functions, the evolution from requestIdleCallback to the custom Scheduler introduced in v16.10, and visualizations of the effect‑list construction.
Conclusion
Fiber is a core part of React’s design, enabling incremental rendering, pause‑and‑resume capabilities, and fine‑grained priority scheduling. Understanding Fiber helps developers grasp why certain lifecycle methods are deprecated and how future features like async rendering will be built.
References
react-fiber-architecture
In‑depth explanation of state and props update in React
In‑depth overview of the new reconciliation algorithm in React
The how and why on React’s usage of linked list in Fiber
Effect List – another Fiber linked‑list construction process
js‑ntqfill
NetEase Cloud Music Tech Team
Official account of NetEase Cloud Music Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.