在 React 18 之前,React 就已经对 state 更新进行批处理了,仅支持浏览器事件的批处理,但是不包含 Promise,setTimeout,native event handlers。本次更新,对批处理进行了改进,所有状态都会启动批处理。
先来看看在 React17 中进行的批处理
下面的代码,当点击 Click Me! 执行 handleOnClick()函数后,会将 2 个状态更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { useState } from "react"; import "./App.css"; const App = () => { const [additionCount, setAdditionCount] = useState(0); const [subtractionCount, setSubtractionCount] = useState(0);
console.log("Component Rendering");
const handleOnClick = () => { setAdditionCount(additionCount + 1); setSubtractionCount(subtractionCount - 1); };
return ( <div> <button style={{ width: "50%", height: "30%" }} onClick={() => { handleOnClick(); }} > Click Me! </button> <div>Add Count: {additionCount}</div><div>Substraction Count: {substractionCount}</div> </div> ); }; export default App;
|
注意看控制台,console.log(“Component Rendering”) 只打印了一次,说明只发生了一次更新。
这说明 react 已经把 2 次 setState 合并成了一次,进行了批处理。

再来看看 React17 不能进行批处理的情况
当遇到非浏览器事件时,批处理就无效了,下面的代码以 fetch 异步事件为例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const handleOnClickAsync = () => { fetch(“https: setAdditionCount(additionCount + 1); setSubstractionCount(substractionCount — 1); }); };
<button style={{ width: “50%”, height: “30%” }} onClick ={() => { handleOnClickAsync(); }} > Click Me Async Call! </button>
|
可以看到控制台中,输出了 2 次,说明页面刷新了 2 次,react 没有进行合并批处理。

这样会有什么问题呢
对于小应用来说,重新渲染不会产生重大影响。但是,随着对于大型项目,嵌套组件的数量会很多。因此,如果父组件执行状态更新,则整个组件树将在每次状态更新时重新渲染,这样会拖慢应用的速度。
React 18 改进了批处理
在 React v18 中从任何位置调用的状态更新将默认进行批处理。这将批处理状态更新,包括浏览器事件处理程序、异步操作、超时和间隔。
下面看一个案例,安装最新的 react 版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import logo from "./logo.svg"; import "./App.css"; import { useState } from "react"; const App = () => { const [count, setCount] = useState(0); const [clicked, setClicked] = useState(false); console.log("React 18 Application Re-Rendering"); const handleClick = () => { setClicked(!clicked); setCount(count + 1); };
const handleAsyncClick = () => { fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => { setClicked(!clicked); setCount(count + 1); }); };
const handleTimeOutClick = () => { setTimeout(() => { setClicked(!clicked); setCount(count + 1); }); };
return ( <div className="App"> {" "} <header className="App-header"> <div> Count: {count} </div><div> Clicked: {clicked} </div> <button onClick={handleClick}> Event Handler </button>{" "} <button onClick={handleAsyncClick}> Async Handler </button> <button onClick={handleTimeOutClick}> Timeout Handler </button> </header> </div> ); };
export default App;
|
上面的代码,每个事件处理程序中都会发生两个状态更新。
分别点击三个按钮,可以看到在浏览器控制台中打印三个日志,而不是 6 个日志,说明 react 进行了合并批处理。

阻止批处理
某些情况下不能使用批处理,例如第二个状态的更新依赖第一个状态的结果。
react 提供了 flushSync 来实现阻止批处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { flushSync } from "react-dom";
const App = () => { console.log("update"); const [count, setCount] = useState(0); const [clicked, setClicked] = useState(false);
const handleClick = () => { flushSync(() => { setCount(count + 1); }); setClicked(true); };
return ( <> <div> Count1: {count} </div> <button onClick={handleClick}> Event Handler </button>{" "} </> ); };
|
可以看到,当点击 Event Handler 时,输出了 2 次 update,说明 flushSync 中的状态更新被单独执行了,没有合并。