issue published by React author Dan Abramov 16183a5645dd36, and there is a link to the original text at the end of the article. The article is easy to understand and is very readable, so share it with you. Friends who are interested in React18 are welcome to learn and discuss.
Overview
React 18 adds out-of-the-box performance improvements by executing more batches by default, eliminating the need for manual batch updates in the application or library code. This article will explain what batch processing is, how it worked before, and what has changed.
Note: This is an in-depth feature that we don't want most users to consider. But it may be closely related to evangelists and react library developers.
What is batch processing
Batch processing is an operation where React groups multiple status updates into a single re-render for better performance.
For example, if you have two status updates in the same click event, React will always batch them into one re-render. If you run the following code, you will see that each time you click, React only performs one rendering, even though you set the state twice:
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // Does not re-render yet
setFlag(f => !f); // Does not re-render yet
// React will only re-render once at the end (that's batching!)
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
- ✅ Demonstration of are reflected in the event handler. (Please note that each click in the console renders once.)
This is very useful for performance because it avoids unnecessary re-rendering. It also prevents your component from presenting a "half-complete" state where only one state variable is updated, which may cause errors. This may remind you of a restaurant waiter who does not go to the kitchen when you choose your first dish, but waits for you to complete your order.
However, React's batch update time is not consistent. For example, if you need to get data and then update the status on handleClick
will not batches, and perform two independent updates instead of 16183a5645deb5.
This is because React past only in browser event (such as clicking) during batch updates, but here we are in the event has been processed (in the fetch callback) after update status:
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 17 and earlier does NOT batch these because
// they run *after* the event in a callback, not *during* it
setCount(c => c + 1); // Causes a re-render
setFlag(f => !f); // Causes a re-render
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
Before React 18, we only update in batches during React event handlers. By default, React does not perform batch operations on promises, setTimeout, native event handlers, or other events that React does not batch by default.
What is automatic batch processing?
Starting with React 18's createRoot
, all updates will be automatically batched, no matter where they come from.
This means that timeouts, promises, native event handlers or any other event will be batched in the same way as updates within React events. We hope this will result in less rendering work and thus better performance in your application:
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 18 and later DOES batch these:
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
- ✅ demo: React 18
createRoot
batch processing in addition to event processing! (note that every click in the console is rendered once!) - 🟡 demo: React 18 with legacy
render
retains the old behavior (note that the console is rendered twice per click.)
Note: As part of the adoption of React 18, you are upgradecreateRoot
. The old behaviorrender
exists only to make it easier to conduct production experiments on the two versions.
No matter where the update occurs, React will automatically update in batches, so:
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}
Same as this: (setTimeout)
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
}, 1000);
Same as this: (fetch)
fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
})
The behavior is the same as this: (addEventListener)
elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// React will only re-render once at the end (that's batching!)
});
Note: React is only updated in batches in safe and stable scenarios.
For example, React ensures that for each user-initiated event (such as a click or keypress), the DOM is fully updated to before the next event.
For example, this ensures that a form that is disabled at the time of submission cannot be submitted twice.
What if you don't want to batch process?
Generally, batch processing is safe, but some code may rely on reading something from the DOM immediately after the state changes. For these use cases, you can use ReactDOM.flushSync()
opt out of batch processing:
import { flushSync } from 'react-dom'; // Note: react-dom, not react
function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
});
// React has updated the DOM by now
flushSync(() => {
setFlag(f => !f);
});
// React has updated the DOM by now
}
We don't want this kind of scene to appear often.
Does it affect Hooks?
If you use Hooks, we hope that automatic batch processing will "work normally" in most cases. (If not, please let us know!)
Does it have any impact on Classes?
updates during the React event handler are always batched, so there are no changes to these updates.
There are edge cases in class components, which can be a problem.
The class component has a strange place to implement, it can read the status update inside the event synchronously. This means that you will be able to setState
read between calls this.state
:
handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
// { count: 1, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
In React 18, this is no longer the case. Since all updates to setTimeout
are batched, React will not setState
is called synchronously for the first time-rendering will happen the next time the browser is scheduled. So the rendering has not happened yet:
handleClick = () => {
setTimeout(() => {
this.setState(({ count }) => ({ count: count + 1 }));
// { count: 0, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
Please refer to code .
If this is an obstacle to upgrading to React 18, you can use ReactDOM.flushSync
force the update, but we recommend use it with caution:
handleClick = () => {
setTimeout(() => {
ReactDOM.flushSync(() => {
this.setState(({ count }) => ({ count: count + 1 }));
});
// { count: 1, flag: false }
console.log(this.state);
this.setState(({ flag }) => ({ flag: !flag }));
});
};
Please refer to code .
This problem does not affect function components with Hooks, because the setting state does not update existing variables useState
function handleClick() {
setTimeout(() => {
console.log(count); // 0
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
console.log(count); // 0
}, 1000)
Although this behavior may be surprising when you adopt Hooks, it paves the way for automatic batch processing.
What about unstable_batchedUpdates
Some React libraries use this undocumented API to force setState
external event handler:
import { unstable_batchedUpdates } from 'react-dom';
unstable_batchedUpdates(() => {
setCount(c => c + 1);
setFlag(f => !f);
});
This API still exists in 18, but no longer needed because batch processing happens automatically. We will not remove it in 18, although it may be removed in a future major version after popular libraries no longer depend on its existence.
Original address: https://github.com/reactwg/react-18/discussions/21
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。