首页 > 解决方案 > 为什么每个状态钩子更新都会导致异步代码中的单独重新渲染?

问题描述

一个组件中使用了两个useState钩子。如果我们允许事件循环在修改状态之前“重排”,每个钩子都会导致单独的重新渲染。

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default function App() {
  const [foo, setFoo] = useState("initial_foo");
  const [bar, setBar] = useState("initial_bar");

  const onAction = useCallback(async () => {
      await sleep(0); // this is the culprit
      setFoo("updated_foo");
      setBar("updated_bar");
  }, []);

  console.log(`rendering foo:${foo} bar:${bar}`);

  return (
    <>
      <button onClick={onAction}>run test</button>
      <div>{"foo: " + foo}</div>
      <div>{"bar: " + bar}</div>
    </>
  );
}

控制台输出await sleep(0)

rendering foo:initial_foo bar:initial_bar
rendering foo:updated_foo bar:initial_bar
rendering foo:updated_foo bar:updated_bar

如果我们删除await sleep(0);,一切都会按预期工作:setFoosetBar导致一次重新渲染。

没有控制台输出await sleep(0)

rendering foo:initial_foo bar:initial_bar
rendering foo:updated_foo bar:updated_bar

演示 (在演示控制台输出重复,因为它启用了 React 严格模式)

更新:

问题已在React 项目 repo中创建

标签: javascriptreactjsreact-hooks

解决方案


在 Promise 中(以及与此相关的计时器)setState是同步的。因此,您有两个重新渲染(首先是 for setFoo,然后是 for setBar),而不是组合(批处理)一个。

这是一个例子:

如果您在 Promise按钮​​中单击setState,您将在每个状态之后记录,但如果您在 eventHandler 中rerender单击setState,您将首先获得两个 setState 日志,然后只有一个重新渲染。这种行为应该在 React 17 中改变,其中状态更新只会是异步的。

function App() {
  const [foo, setFoo] = React.useState(); 
  const [bar, setBar] = React.useState();
  function handlerPromise() {
    Promise.resolve().then(() => {
      console.log("setFoo");
      setFoo({});
      console.log("setBar");
      setBar({});
    }) 
  }
      function handler() {
      console.log("setFoo");
      setFoo({});
      console.log("setBar");
      setBar({});

  }
  console.log("rerender");
  return (
  <div>
  <button onClick={handlerPromise}>setState in Promise</button>
  <button onClick={handler}>setState in an eventHandler</button>
  </div>
  )
}



ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.js"></script>

<div id="root"></div>


推荐阅读