React Batching Updates Notes
Batching is a core optimization technique in React that combines multiple state updates into a single render to improve performance. Understanding the batching mechanism is key to writing more efficient React.
1. Batching
Batching is a mechanism that combines multiple state updates into a single render. Consider the following scenario:
function Counter() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
function handleClick() {
setCount1(count1 + 1);
setCount2(count2 + 1);
// 两次状态更新,React 会合并为一次渲染
}
return (
<div>
<p>Count 1: {count1}</p>
<p>Count 2: {count2}</p>
<button onClick={handleClick}>增加</button>
</div>
);
}
After clicking the button, although setState is called twice, the component re-renders only once. This is the effect of batching.
2. Batching in React 17 and Earlier
2.1 Implementation Principle
// React 17 简化版实现
let isBatchingUpdates = false;
let updateQueue = [];
function batchedUpdates(callback) {
const alreadyBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return callback();
} finally {
isBatchingUpdates = alreadyBatchingUpdates;
if (!isBatchingUpdates) {
flushBatchedUpdates();
}
}
}
function flushBatchedUpdates() {
while (updateQueue.length) {
const update = updateQueue.shift();
update.perform();
}
}
Key points:
isBatchingUpdatesflag determines whether batching is in progressbatchedUpdatesfunction enables batching mode, flushes the queue after executing the callback- React automatically enables batching in event handlers
2.2 Limitations
In React 17 and earlier, batching is mainly limited to event handlers:
// React 17: setTimeout 中的更新不会批量处理
function handleClick() {
setTimeout(() => {
setCount1(c => c + 1); // 触发一次渲染
setCount2(c => c + 1); // 再触发一次渲染
}, 1000);
}
Note: state updates in async callbacks are not batched; each
setStatetriggers a separate render.
3. Automatic Batching in React 18
3.1 Core Improvements
React 18 introduces automatic batching, which merges state updates even inside asynchronous code:
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';
function Counter() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
function handleAsyncUpdate() {
setTimeout(() => {
setCount1(c => c + 1); // 自动合并
setCount2(c => c + 1); // 自动合并
// 只会触发一次渲染!
}, 1000);
}
return (
<div>
<p>Count 1: {count1}</p>
<p>Count 2: {count2}</p>
<button onClick={handleAsyncUpdate}>异步更新</button>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);
3.2 Performance Gains
| Scenario | React 17 | React 18 |
|---|---|---|
| Multiple updates in event handlers | 1 render | 1 render |
| Multiple updates in setTimeout | Multiple renders | 1 render |
| Multiple updates in Promise.then | Multiple renders | 1 render |
| Multiple updates in fetch callbacks | Multiple renders | 1 render |
3.3 Manual Batching
If you need to batch updates manually in specific scenarios, you can use unstable_batchedUpdates:
import { unstable_batchedUpdates } from 'react-dom';
function handleClick() {
unstable_batchedUpdates(() => {
setCount1(c => c + 1);
setCount2(c => c + 1);
});
}
Tip: In React 18, most scenarios don’t require manual calls; the framework handles it automatically.
4. Batching flow diagram
graph TD
A[状态更新触发] --> B{是否在批量上下文中?}
B -->|是| C[加入更新队列]
B -->|否| D[立即处理更新]
C --> E{批量操作结束?}
E -->|否| F[等待更多更新]
E -->|是| G[执行批量渲染]
D --> H[执行单次渲染]
G --> I[组件重新渲染]
H --> I
5. Combining with other React 18 features
5.1 Transition API
Batching combined with startTransition lets you distinguish urgent updates from non-urgent ones:
import { useTransition, useState } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value); // 紧急更新 - 输入框立即响应
startTransition(() => {
// 非紧急更新 - 搜索结果批量处理
searchAPI(value);
});
}
}
5.2 Concurrent rendering
React 18’s concurrent rendering allows interrupting and resuming renders, marking a major architectural upgrade for React.
5.2.1 What is concurrent rendering
Traditional React (17 and earlier) rendering is synchronous: once rendering starts, it blocks the main thread until it completes. If the component tree is large, user interactions may be delayed, causing jank.
Concurrent rendering is different: React can prepare multiple versions of the UI simultaneously and switch between them dynamically based on the priority of user interactions.
graph TD
A[用户输入] --> B{紧急更新?}
B -->|是| C[高优先级渲染]
B -->|否| D[低优先级渲染]
C --> E[立即响应]
D --> F[可中断]
F --> G[更紧急的更新到达]
G --> C
F --> H[完成渲染]
5.2.2 Core concept: Fiber architecture
React 18’s concurrent rendering is built on the Fiber architecture. Fiber is an internal React data structure that splits rendering work into small units:
// 传统渲染:一次性完成
// 组件A → 组件B → 组件C → DOM 更新
// Fiber 渲染:分片完成
// [工作单元1] → [工作单元2] → [工作单元3] → ... → DOM 更新
// ↑ ↓
// 中断 恢复
5.2.3 useDeferredValue
useDeferredValue is used to defer rendering of non-critical content:
import { useDeferredValue, useState, useMemo } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// 大量数据的筛选 - 使用延迟值
const filteredItems = useMemo(
() => items.filter(item =>
item.name.toLowerCase().includes(deferredQuery.toLowerCase())
),
[deferredQuery]
);
// 样式区分:延迟渲染时显示不同状态
const isStale = query !== deferredQuery;
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="搜索..."
/>
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{filteredItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
</div>
);
}
5.2.4 useTransition
startTransition marks non-urgent updates:
import { useTransition, useState } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [activeTab, setActiveTab] = useState('posts');
function handleTabChange(tab) {
// 立即更新 UI 状态
setActiveTab(tab);
// 但内容切换可以延迟
startTransition(() => {
// 这个更新会被标记为低优先级
fetchTabData(tab);
});
}
return (
<div>
<button onClick={() => handleTabChange('posts')}>文章</button>
<button onClick={() => handleTabChange('comments')}>评论</button>
<button onClick={() => handleTabChange('settings')}>设置</button>
{isPending && <LoadingSpinner />}
<TabContent activeTab={activeTab} />
</div>
);
}
5.2.5 Concurrent rendering workflow
sequenceDiagram
participant User as 用户
participant React as React 引擎
participant Fiber as Fiber 调度器
participant DOM as DOM
User->>React: 输入搜索关键词
React->>Fiber: 创建高优先级任务
Fiber->>Fiber: 中断低优先级渲染
Fiber->>DOM: 优先更新输入框
Note over Fiber: 用户看到即时反馈
Fiber->>DOM: 完成搜索结果渲染
Note over DOM: 显示搜索结果
5.2.6 Use case comparison
| Scenario | Solution | Notes |
|---|---|---|
| Search input + results list | useDeferredValue | Input responds immediately, results render deferred |
| Tab switching | useTransition | Switch button responds immediately, content can be deferred |
| Large list scrolling | useDeferredValue | Keeps scrolling smooth |
| Form validation | No handling needed | Validation is typically urgent |
5.2.7 Caveats
// ❌ 错误:不要对状态值使用 useDeferredValue
function BadExample() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// 这没有意义,query 和 deferredQuery 本质相同
return <div>{deferredQuery}</div>;
}
// ✅ 正确:deferredQuery 用于派生计算
function GoodExample() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const filteredItems = useMemo(
() => expensiveFilter(allItems, deferredQuery),
[deferredQuery]
);
return <List items={filteredItems} />;
}
5.2.8 Relationship with batching updates
Concurrent rendering and batching updates are complementary:
function App() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
function handleClick() {
setCount(c => c + 1); // 高优先级更新 - 自动批量
startTransition(() => {
// 低优先级更新 - 可中断
setFilterValue(newValue);
setSearchResults(newResults);
});
}
}
- Batching updates: merges multiple
setStateinto a single render - Concurrent rendering: Determines render order and whether to interrupt based on priority
Combined, React 18 can intelligently handle various update scenarios, providing a smooth user experience.
6. Summary
React’s batching update mechanism has undergone significant evolution:
- React 17: Automatic batching only in event handlers
- React 18: Automatic batching extended to all scenarios
This improvement brings notable performance gains. Developers no longer need to worry about the context of state updates; React intelligently merges renders.