React Runtime Optimization: From React 15 to 18 – Architecture, Scheduling, and New Features
This article provides a comprehensive overview of React's runtime optimization strategies across versions 15 to 18, explaining the evolution of its architecture, the introduction of Fiber, concurrent rendering, scheduling, priority lanes, and new APIs such as Suspense, startTransition, and useDeferredValue, while including detailed code excerpts and practical insights for developers.
Overview
The author, a senior front‑end engineer from Douyin's front‑end security team, shares a long, in‑depth written version of a recent talk on React runtime optimization, covering the evolution of React's core architecture from version 15 through 18 and the key concepts that enable better performance.
Design Ideas of Major JS Frameworks
React is a runtime‑centric framework that generates a virtual DOM and uses a diff algorithm to compute minimal DOM updates, while Svelte relies on compile‑time transformations and Vue balances runtime and compile‑time optimizations.
What Is Compile‑Time Optimization?
Vue's template syntax allows the compiler to pre‑analyze static nodes and generate patch flags, enabling the renderer to skip unnecessary work.
Runtime Optimization – React 15
React 15 introduced a simple Reconciler and Renderer split and added a basic batch update mechanism, which caused setState to be synchronous but batched within the same event loop. The article includes a code example of a class component calling setState multiple times and explains the resulting console output.
class Example extends React.Component {
constructor() {
super();
this.state = { val: 0 };
}
componentDidMount() {
this.setState({ val: this.state.val + 1 });
console.log(this.state.val);
this.setState({ val: this.state.val + 1 });
console.log(this.state.val);
setTimeout(() => {
this.setState({ val: this.state.val + 1 });
console.log(this.state.val);
this.setState({ val: this.state.val + 1 });
console.log(this.state.val);
}, 0);
}
render() { return null; }
}The batch processing in React 15 is "semi‑automatic" because it cannot handle asynchronous callbacks.
React 16 – Enabling Concurrent Mode
React 16 added a Scheduler, Fiber, and a new Reconciler that makes the rendering process interruptible. The article explains how Fiber nodes store component type, state, and links to child/sibling/parent, and how double buffering (current Fiber vs. work‑in‑progress Fiber) allows updates to be paused and resumed.
function FiberNode(tag, pendingProps, key, mode) {
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
}Concurrent Mode splits rendering into an interruptible "render" phase and a non‑interruptible "commit" phase, and introduces new lifecycle handling (e.g., getDerivedStateFromProps) to avoid side effects during the render phase.
Scheduler and Priority Control
The Scheduler uses a min‑heap task queue where each task has an expiration time derived from its priority (Immediate, UserBlocking, Normal, Low, Idle). The article shows how React maps these priorities to lanes, a bit‑mask representation that enables fine‑grained scheduling and better handling of mixed CPU‑bound and I/O‑bound tasks.
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}When a higher‑priority task arrives (e.g., user input), the current low‑priority work is paused, and the scheduler resumes it later.
React 17 – Multi‑Version Coexistence and Lanes
React 17 moves event delegation to the root container instead of the document, allowing multiple React versions on the same page. It also introduces the Lanes system to replace expirationTime, providing a more flexible priority model.
React 18 – Flexible Concurrent Rendering
React 18 introduces createRoot (enabling concurrent rendering by default), startTransition, useTransition, and useDeferredValue to let developers mark updates as non‑urgent. It also adds full Suspense support for server‑side rendering, allowing fallback UI to stream while the main component loads.
import { startTransition } from 'react';
// Urgent update
setInputValue(input);
// Transition (low priority)
startTransition(() => {
setSearchQuery(input);
});useDeferredValue delays rendering of a value only when necessary, avoiding the fixed latency of traditional debouncing.
const [deferredText] = useDeferredValue(text, { timeoutMs: 2000 });SSR Support for Suspense
React 18 allows on the server: the fallback UI streams first, and once the async component resolves, React streams the final HTML to replace the fallback.
<Layout>
<Article />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</Layout>Conclusion
The article recommends reading two community resources—"React Source Code Illustration Series" and "React Technical Secrets"—to grasp the overall architecture before diving into specific modules.
TikTok Frontend Technology Team
We are the TikTok Frontend Technology Team, serving TikTok and multiple ByteDance product lines, focused on building frontend infrastructure and exploring community technologies.
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.