[英]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.这就是为什么你应该总是依赖像
useEffect
或useMemo
这样的 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.