React中如何在useEffect获取数据时批量执行多个setState?异步场景下避免多次重渲染的方案
为什么你的setState没有被批量处理?
你观察得很准——问题确实出在异步代码的执行时机上。让我拆解一下:
批量更新的规则
React的批量更新并不是在所有场景下都生效:
- 在React同步上下文里(比如组件的事件处理函数、useEffect的同步回调块),多个setState会被合并成一次重渲染。
- 但在异步回调(比如
async/await后续的代码、Promise.then、setTimeout)里,React 17及更早版本不会自动批量处理setState,每调用一次setState就会触发一次重渲染。
你的代码里,setWorldData和setCountriesData是在await Promise.all()之后执行的,这属于异步回调的范畴,所以两次setState会分别触发重渲染:第一次是setWorldData更新后,组件重渲染,控制台输出获取到的worldData;第二次是setCountriesData更新后,组件再次重渲染,这时候worldData已经是最终值了,所以控制台又输出一次相同的数组。
解决方法:让组件只重渲染一次
有几种可靠的方式实现这个需求:
1. 合并多个state为一个对象
这是最推荐的方案,既符合React的状态管理思路,又能从根源上减少不必要的重渲染:
function App() { // 合并两个state为一个对象 const [covidData, setCovidData] = useState({ worldData: null, countriesData: null }) useEffect(() => { const fetchData = async () => { try { const [countries, world] = await Promise.all([ fetch("https://disease.sh/v3/covid-19/countries?yesterday=true").then(res => res.json()), fetch("https://disease.sh/v3/covid-19/all?yesterday=true").then(res => res.json()) ]) // 一次setState更新所有数据 setCovidData({ worldData: world, countriesData: countries }) } catch (err) { console.log(err) } } fetchData(); }, []); console.log(covidData.worldData); }
这样只需要调用一次setCovidData,组件只会重渲染一次。
2. 手动触发批量更新(React 17及更早)
如果你不想合并state,可以使用React提供的unstable_batchedUpdates API(注意:它标记为不稳定,未来可能会有变动),手动把多个setState包裹在批量更新里:
import { unstable_batchedUpdates } from 'react-dom'; // 注意是react-dom而不是react // ... useEffect(() => { const fetchData = async () => { try { const [countries, world] = await Promise.all([ fetch("https://disease.sh/v3/covid-19/countries?yesterday=true").then(res => res.json()), fetch("https://disease.sh/v3/covid-19/all?yesterday=true").then(res => res.json()) ]) unstable_batchedUpdates(() => { setWorldData(world) setCountriesData(countries) }) } catch (err) { console.log(err) } } fetchData(); }, []);
3. 升级到React 18并使用createRoot
React 18引入了自动批量更新,不管是同步还是异步场景,都会自动合并多个setState。但要注意:这个特性只在使用createRoot渲染组件时生效,老的ReactDOM.render不会开启它。
比如你的入口文件应该改成:
import { createRoot } from 'react-dom/client'; import App from './App'; const root = createRoot(document.getElementById('root')); root.render(<App />);
升级后,你的原代码里的两次setState会被自动批量处理,组件只会重渲染一次。
内容的提问来源于stack exchange,提问作者hoopslam
相关产品推荐
相关产品推荐

