简体   繁体   English

React Hooks `useState` 设置函数展示同步和异步行为

[英]React Hooks `useState` set function exhibits sync as well as async behaviour

Consider a button callback listener in React Functional components defined as below -考虑 React 功能组件中的按钮回调侦听器,定义如下 -

Here's the codesandbox link to repro this.这是重现此内容的代码框链接

const [setCtr, ctr] = useState(0);
const btnClicked = () => {
    console.log("button callback");
    setCtr((x) => {
      console.log("setCtr executed...");
      return x + 1;
    });
    console.log("button callback ends");
  };

When I click on the button, the first time, setCtr executes synchronously but after that it always (tried 20 times) runs asynchronously.当我单击按钮时,第一次setCtr同步执行,但之后它总是(尝试 20 次)异步运行。

Here's the output for first button click这是第一次单击按钮的输出

button callback 
setCtr executed... 
button callback ends 

Here's the output for subsequent button clicks这是后续按钮单击的输出

button callback 
button callback ends 
setCtr executed... 

button callback 
button callback ends 
setCtr executed... 

button callback 
button callback ends 
setCtr executed... 

button callback 
button callback ends 
setCtr executed... 

This is confusing.这令人困惑。 How exactly does the set function in useState work? useState中的 set 函数究竟是如何工作的? If it was truly an async function, we should always get button callback ends before setCtr executed... .如果它真的是一个异步函数,我们应该总是在setCtr executed...之前让button callback ends ......。
This makes me think sometimes it behaves as synchronous function, while sometimes as asynchronous function.这让我觉得有时它表现为同步函数,而有时表现为异步函数。

This statement is not totally true: If it was truly an async function, we should always get button callback ends before setCtr executed.... React 18 batches all setStates, but it has also a priority queue for every instruction that you write inside handlers, synchronous or asynchronous, so you should not rely on the callback of the setState to happen before or after something else, unless you explicitely say to React to execute it synchronously:这句话并不完全正确:如果它真的是一个异步函数,我们应该总是在 setCtr 执行之前让按钮回调结束...... React 18 批处理所有 setStates,但它还有一个优先级队列,用于您在处理程序中编写的每条指令,同步或异步,所以你不应该依赖 setState 的回调发生在其他事情之前或之后,除非你明确告诉 React 同步执行它:

 function App() { const [ctr, setCtr] = React.useState(0); const btnClicked = () => { console.log("button callback", ctr); ReactDOM.flushSync(() => setCtr((x) => { console.log("setCtr executed...",x); return x + 1; }) ); console.log("button callback ends", ctr); }; console.log("RERENDERING X:", ctr) return ( <div className="App"> {ctr} <br /> <button onClick={btnClicked}>Add</button> </div> ); } ReactDOM.render(<App/>, root)
 <div id="root"></div> <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

React, especially React 18 applies some heavy code optimizations so don't think it just executes the code you write inside handlers "as is". React,尤其是 React 18 应用了一些繁重的代码优化,所以不要认为它只是“按原样”执行你在处理程序中编写的代码。 As you can see, using flushSync explicitely tells React to flush all the setStates immediately when invoked, so it stops all synchronous code inside the handler, re-renders the component, and then keeps executing the rest of the code.如您所见,使用flushSync明确告诉 React 在调用时立即刷新所有 setState,因此它会停止处理程序中的所有同步代码,重新渲染组件,然后继续执行其余代码。 You can note, that the state of the last log is stale when it gets executed, and it refers to the previous value, since the instruction was memoized on the value of the state at the time you declared it.您可以注意到,最后一个log的状态在执行时是陈旧的,并且它指的是先前的值,因为该指令在您声明它时就已记录在该状态的值上。 That's another important feature of React.这是 React 的另一个重要特性。 That's why you should always rely on Hooks like useEffect or useMemo with the deps that you are interested to track, to perform operations after the state updated.这就是为什么你应该总是依赖像useEffectuseMemo这样的 Hooks 以及你有兴趣跟踪的 deps,在状态更新后执行操作。

Reference: https://reactjs.org/docs/react-dom.html#flushsync参考: https ://reactjs.org/docs/react-dom.html#flushsync

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM