Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
Join the discussion
Loading comments...
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.
React 如何将多个 setState 调用合并为单次重新渲染以提升性能。
状态批处理意味着 React 收集同一同步块中的多个 setState 调用,并在单次重新渲染过程中处理它们。React 不会在每个 setState 后重新渲染,而是等到当前批次结束,一次性应用所有更新,然后恰好渲染一次。React 18 将自动批处理扩展到异步代码和原生事件监听器。
当你在点击处理器中调用 setA(1) 和 setB(2) 时,React 不会在 setA 后立即重新渲染。它将两个更新排入队列并调度单次重新渲染。你的组件以两个新值同时应用的状态渲染一次。
每个 setState 调用将一个 Update 对象追加到 hook 的循环队列中。React 将 Fiber 标记为有待处理工作,并调度一个微任务(或回调)来刷新队列 — 但只在当前同步代码完成后。
在 React 17 中,批处理只发生在 React 事件处理器内部。setTimeout、Promise 或 fetch 回调中的 setState 调用会触发单独的重新渲染。React 18 自动批处理所有更新,无论来源如何。
如果你需要同步 DOM 更新(例如在 state 变化后测量布局),调用 ReactDOM.flushSync(() => setState(...))。这迫使 React 立即同步刷新更新,绕过批处理。
当多个 setState 调用依赖上一个值时,使用函数形式:setState(prev => prev + 1)。React 按队列顺序应用这些更新,因此每次更新都能看到上一次的结果 — 而不是渲染时的值。
React 18 特性:所有 setState 调用都被批处理,即使在 setTimeout、Promise 和原生事件监听器中。减少不必要的重新渲染。
每个 hook 对象上的循环链表,存储待处理的 setState 调用。在每批结束时刷新 — 所有更新按顺序应用。
强制 React 同步立即刷新待处理的 state 更新。绕过批处理。仅在需要在 state 变化后测量 DOM 时使用。
setState(prev => newValue)。保证接收最新的 state 值,即使更新被批处理或异步排队。
批处理减少渲染次数。没有批处理:N 次 setState 调用 = N 次渲染。有批处理:N 次 setState 调用 = 1 次渲染。
1// React 18: ALL of these batch into 1 render23// Inside event handler (always batched)4function handleClick() {5 setCount(c => c + 1); // queued6 setFlag(true); // queued7 setName('Alice'); // queued8 // 1 render happens here9}1011// Inside setTimeout — NOW also batched in React 1812setTimeout(() => {13 setCount(c => c + 1); // queued14 setFlag(true); // queued15 // 1 render (React 17 would have triggered 2!)16}, 0);1718// Opt out of batching when needed:19import { flushSync } from 'react-dom';20flushSync(() => setCount(1)); // renders immediately21flushSync(() => setFlag(true)); // renders again22// 2 renders total
批处理是 React 最重要的性能优化之一。没有它,触发 3 次 state 更新的单次用户交互将导致 3 次完整的渲染 + 协调 + 提交周期。有了批处理,始终只有 1 次。React 18 的自动批处理对异步代码尤其有价值 — 服务器响应、WebSocket 消息和定时器不再会频繁触发渲染器。
A login form validates email, password, and shows/hides error messages. The submit handler calls `setEmailError(...)`, `setPasswordError(...)`, `setIsSubmitting(false)` — three state updates that should be a single render.
In React 17, calling setState inside setTimeout or fetch().then() didn't batch — each call triggered a separate render. Users would see a flash: first the email error appears, then the password error, then the loading spinner disappears. Three renders for one validation check.
React 18's automatic batching ensures all three setState calls result in ONE render, regardless of whether they're inside an event handler, setTimeout, fetch callback, or promise. The user sees all errors and the stopped spinner simultaneously — no flash.
Takeaway: React 18's automatic batching is a free performance upgrade for existing code. If you migrated from React 17 and had flushSync workarounds for batching, you can now remove them — batching works everywhere automatically.
You need to update a component's state and immediately measure the resulting DOM height to pass to a third-party animation library. With batching, the DOM hasn't updated yet when you try to measure.
Automatic batching defers the DOM update. After calling `setExpanded(true)`, the DOM still shows the collapsed state. Measuring `element.offsetHeight` returns the old height. The animation library receives the wrong value.
Wrap the state update in `flushSync()`: `flushSync(() => setExpanded(true))`. This forces React to immediately commit the DOM update synchronously. The next line can safely measure `element.offsetHeight` and get the expanded height. Use sparingly — it opts out of batching's performance benefit.
Takeaway: flushSync is the escape hatch when you need synchronous DOM access after a state update. Common use cases: third-party animation libraries, scroll position restoration, and focus management. Use it only when batching conflicts with imperative DOM operations.