Understanding React Fiber: How It Boosts Rendering Performance
This article explains why React introduced the Fiber architecture, how it breaks rendering work into small interruptible units, the role of requestAnimationFrame and requestIdleCallback, the underlying linked‑list data structures, and provides code examples that illustrate the render and commit phases for smoother UI updates.
Fiber Design Philosophy
Fiber is a rewrite of React's core algorithm introduced in React 16 to split rendering work into small units, allowing the browser to interleave rendering with user interactions and avoid long‑blocking tasks.
Why Fiber is Needed
Browsers render frames at ~60 fps; if a task exceeds 16 ms the UI becomes janky. The article lists the seven steps performed in each frame (input, timers, begin frame, requestAnimationFrame, layout, paint, idle) and explains how long‑running tasks block the main thread.
From Reconciliation to Fiber
Before Fiber, React performed a recursive diff of the virtual DOM and updated synchronously, which could monopolize the main thread. Fiber breaks the work into interruptible units, uses requestIdleCallback and requestAnimationFrame to schedule work, and can pause and resume rendering.
Comparison with Vue
Vue uses a template‑watcher model that already splits updates into very small tasks, so it does not need a Fiber‑like scheduler.
Core APIs
requestAnimationFrameschedules a callback before the next paint; an example animates a <div> width from 0 to 100 px and logs frame intervals.
let btn = document.getElementById('start');
let div = document.getElementById('div');
let start = 0;
let allInterval = [];
const progress = () => {
div.style.width = div.offsetWidth + 1 + 'px';
div.innerHTML = div.offsetWidth + '%';
if (div.offsetWidth < 100) {
let current = Date.now();
allInterval.push(current - start);
start = current;
requestAnimationFrame(progress);
} else {
console.log(allInterval);
}
};
btn.addEventListener('click', () => {
div.style.width = 0;
let current = Date.now();
start = current;
requestAnimationFrame(progress);
});requestIdleCallbackruns low‑priority work when the browser has idle time; the article shows a task queue that executes tasks while time remains, and a version where each task sleeps >16 ms, forcing multi‑frame execution.
let taskQueue = [
() => { console.log('task1 start'); console.log('task1 end'); },
() => { console.log('task2 start'); console.log('task2 end'); },
() => { console.log('task3 start'); console.log('task3 end'); }
];
const performUnitWork = () => taskQueue.shift();
const workloop = deadline => {
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && taskQueue.length) {
performUnitWork();
}
if (taskQueue.length) {
requestIdleCallback(workloop, { timeout: 1000 });
}
};
requestIdleCallback(workloop, { timeout: 1000 });Fiber Data Structure
Fiber nodes form a singly‑linked list tree with properties such as type, key, stateNode, child, sibling, return, memoizedState, pendingProps, etc. The article provides a minimal Update and UpdateQueue implementation that stores payload and a pointer to the next update.
class Update {
constructor(payload, nextUpdate) {
this.payload = payload;
this.nextUpdate = nextUpdate;
}
}
class UpdateQueue {
constructor() {
this.baseState = null;
this.firstUpdate = null;
this.lastUpdate = null;
}
enqueueUpdate(update) {
if (!this.firstUpdate) {
this.firstUpdate = this.lastUpdate = update;
} else {
this.lastUpdate.nextUpdate = update;
this.lastUpdate = update;
}
}
forceUpdate() {
let state = this.baseState || {};
let cur = this.firstUpdate;
while (cur) {
const next = typeof cur.payload === 'function' ? cur.payload(state) : cur.payload;
state = { ...state, ...next };
cur = cur.nextUpdate;
}
this.firstUpdate = this.lastUpdate = null;
this.baseState = state;
return state;
}
}
let queue = new UpdateQueue();
queue.enqueueUpdate(new Update({ name: 'www' }));
queue.enqueueUpdate(new Update({ age: 10 }));
queue.enqueueUpdate(new Update(state => ({ age: state.age + 1 })));
queue.enqueueUpdate(new Update(state => ({ age: state.age + 1 })));
queue.forceUpdate();
console.log(queue.baseState); // { name: 'www', age: 12 }Traversal and Effect List
The render phase walks the Fiber tree (post‑order) using beginWork and completeUnitOfWork, collects side‑effects into an effect list, and yields to the browser when the frame time runs out.
const beginWork = fiber => console.log(`${fiber.key} start`);
const completeUnitOfWork = fiber => console.log(`${fiber.key} end`);
const performUnitOfWork = fiber => {
beginWork(fiber);
if (fiber.child) return fiber.child;
while (fiber) {
completeUnitOfWork(fiber);
if (fiber.sibling) return fiber.sibling;
fiber = fiber.return;
}
};
let nextUnit = rootFiber;
while (nextUnit) nextUnit = performUnitOfWork(nextUnit);
console.log('reconciliation phase finished');During completeUnitOfWork each fiber merges its own effects into its parent’s firstEffect / lastEffect linked list, building the final effect list for the root.
Commit Phase
The commit phase iterates the effect list and applies INSERT, DELETE, or UPDATE operations to the DOM. This phase is non‑interruptible.
const commitWork = fiber => {
if (!fiber) return;
const parentDom = fiber.return.stateNode;
if (fiber.effectTag === 'INSERT') {
parentDom.appendChild(fiber.stateNode);
} else if (fiber.effectTag === 'DELETE') {
parentDom.removeChild(fiber.stateNode);
} else if (fiber.effectTag === 'UPDATE') {
if (fiber.type === 'TEXT' && fiber.alternate.props.text !== fiber.props.text) {
fiber.stateNode.textContent = fiber.props.text;
}
}
fiber.effectTag = null;
};
const commitRoot = () => {
let fiber = workInProgressRoot.firstEffect;
while (fiber) {
commitWork(fiber);
fiber = fiber.nextEffect;
}
currentRoot = workInProgressRoot;
workInProgressRoot = null;
};Putting It All Together
A simplified work loop uses requestIdleCallback to repeatedly call performUnitOfWork until all work is done, then calls commitRoot to flush changes.
const workLoop = deadline => {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && workInProgressRoot) {
console.log('render phase finished');
commitRoot();
}
requestIdleCallback(workLoop, { timeout: 1000 });
};
requestIdleCallback(workLoop, { timeout: 1000 });The article concludes that Fiber enables smoother user experiences by preventing long‑running tasks from blocking rendering, and encourages readers to explore the React source for deeper details such as task priority and interruption handling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
