对 useEffect 挂钩内的多个 setState() 调用做出反应批量更新
Posted
技术标签:
【中文标题】对 useEffect 挂钩内的多个 setState() 调用做出反应批量更新【英文标题】:React batch updates for multiple setState() calls inside useEffect hook 【发布时间】:2019-11-15 00:34:57 【问题描述】:关于 Dan Abramov 在 SO 上的回答,我发现了以下内容:
Does React keep the order for state updates?
目前(React 16 及更早版本),默认情况下仅对 React 事件处理程序内部的更新进行批处理。有一个不稳定的 API 可在您需要的极少数情况下强制在事件处理程序之外进行批处理。
他还在这个 Github issue 中提到:
https://github.com/facebook/react/issues/10231#issuecomment-316644950
在当前版本中,如果您在 React 事件处理程序中,它们将一起批处理。React 批处理在 React 事件处理程序期间完成的所有 setState,并在退出其自己的浏览器事件处理程序之前应用它们.
但事实是,这个 sn-p 似乎证明useEffect()
内的多个setState
调用的更新是批处理的。
问题
React 是否也总是批量更新 useEffect
内的多个 setState()
调用?它还能在哪里做到这一点?
注意:根据他的回答,在下一个主要版本(可能是 v17)上,React 将默认批处理。
SNIPPET:在useEffect()
内通过多个setState()
调用进行批量更新
function App()
console.log('Rendering app...');
const [myState,setMyState] = React.useState(0);
const [booleanState, setBooleanState] = React.useState(false);
console.log('myState: ' + myState);
console.log('booleanState: ' + booleanState);
React.useEffect(()=>
console.log('Inside useEffect...');
setMyState(1);
setMyState((prevState) => prevState +1);
setMyState(3);
setMyState(4);
setMyState(5);
setBooleanState(true);
,[]);
return(
<div>App - Check out my console!</div>
);
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
【问题讨论】:
【参考方案1】:好问题。这是完成@FranklinOcean 答案的附加信息。
2021 年更新:React 18 即将发生的变化
请参阅 Dan Abramov 在 React 18 中有关此主题的更新,该更新添加了自动批处理:https://github.com/reactwg/react-18/discussions/21
回答当前版本的 React,即 17.0.2 及以下,截至 2021 年。
基于on the following codesandbox:
批量setStuff
调用:
useEffect
块中同步
在合成事件处理程序中同步(由反应管理,例如onClick=handlerFunction
)
每次都会触发重新渲染的非批处理调用:
任何异步代码(上述任何用例中的承诺/异步函数) 非合成事件(事件在反应库外部管理) 包括 XHR 或其他网络回调我会尝试使用未来版本的 react 重新运行沙箱,看看效果如何!
【讨论】:
【参考方案2】:如果状态更新直接发生,React 会批量更新。
批处理:
export default () =>
const [a, setA] = React.useState(0);
const [b, setB] = React.useState(0);
useEffect(() =>
setA(1); setB(1);
,[]);
return (
<div>
<p>A: a</p>
<p>B: b</p>
</div> );
;
单击此按钮将只重新渲染一次组件。
非批处理:
export default () =>
const [a, setA] = React.useState(0);
const [b, setB] = React.useState(0);
useEffect(() =>
setTimeout(() => setA(1); setB(1); , 1000);
, []);
return (
<div>
<p>A: a</p>
<p>B: b</p>
</div> );
;
【讨论】:
【参考方案3】:只是加入一个对我有用的“黑客”
const [dishes, dishesSet] = React.useState(state)
const [isConnectedToDB, isConnectedToDBSet] = React.useState(false)
React.useEffect(() =>
const databaseDishesRef = firebase.database().ref(process.env.FIREBASE_DISHES_REF)
databaseDishesRef.on('value', (snapshot) =>
const value = snapshot.val()
// isConnectedToDBSet(true) // this was not working
dishesSet(() =>
isConnectedToDBSet(true) // had to move it in here to batch the state updates
return Object.entries(value).map(([, dish]) => dish)
)
)
return () =>
databaseDishesRef.off('value')
, [])
替代方法是编写一个“Reac.useEffectfor the
isConnectedToDB”,然后在每道菜更新时触发它..
【讨论】:
以上是关于对 useEffect 挂钩内的多个 setState() 调用做出反应批量更新的主要内容,如果未能解决你的问题,请参考以下文章
在 apollo graphql 的单个 useEffect 挂钩中使用多个查询
无法对未安装的组件执行 React 状态更新(useEffect 反应挂钩)
无法使用 MaterialTable 中的 useEffect 挂钩显示数据