16

在 Dan Abramov 在 SO 上的这个回答中,我发现了以下内容:

React 是否保持状态更新的顺序?

目前(React 16 和更早版本),默认情况下,仅对 React 事件处理程序内的更新进行批处理。有一个不稳定的 API 可以在您需要的极少数情况下强制在事件处理程序之外进行批处理。

他还在这个 Github issue 中提到:

https://github.com/facebook/react/issues/10231#issuecomment-316644950

在当前版本中,如果您在 React 事件处理程序中,它们将被一起批处理。React 批处理在 React 事件处理程序期间完成的所有 setState,并在退出其自己的浏览器事件处理程序之前应用它们。

但事实是,这个片段似乎证明了 a 内多个setState调用的更新useEffect() 是批处理的。

问题

setState()React 是否也总是为内部的多个调用批量更新useEffect?它还能在哪里做到这一点?

注意:根据他的回答,在下一个主要版本(可能是 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"/>

4

3 回答 3

8

好问题。这是完成@FranklinOcean 答案的附加信息。

2021 年更新:React 18 即将发生的变化

请参阅 React 18 中有关此主题的 Dan Abramov 更新,该更新添加了自动批处理:https ://github.com/reactwg/react-18/discussions/21

截至 2021 年,当前版本的 react 为 17.0.2 及以下版本的答案。

基于以下代码框


批量setStuff调用:

  • 同步内联组件功能块(我认为相当于让一个效果在其他效果之前运行,并且没有依赖关系)
  • 在一个useEffect块中同步
  • 在合成事件处理程序中同步(由反应管理,例如onClick={handlerFunction}

每次都会触发重新渲染的非批处理调用:

  • 任何异步代码(上述任何用例中的承诺/异步函数)
  • 非合成事件(在反应库外部管理的事件)
  • 这包括 XHR 或其他网络回调

我将尝试使用未来版本的 react 重新运行沙箱,看看效果如何!

于 2020-03-23T22:38:58.197 回答
4

如果状态更新直接发生,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> ); 
}; 
于 2019-11-19T21:40:26.137 回答
0

只是会加入一个对我有用的“黑客”

  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.useEffect for theisConnectedToDB”,然后在每道菜更新时触发它。

于 2021-05-20T11:36:38.463 回答