useEffect(() => { console.log("render"); }); const handleClick = () => { setC1((c) => c + 1); Promise.resolve().then(() => { setC1((c) => c + 1); }); }; const handleClick2 = () => { Promise.resolve().then(() => { setC1((c) => c + 1); }); setC1((c) => c + 1); };
In the React18 version, why does clicking the handleClick
method cause two renderings, while clicking the handleClick2
method only causes one rendering?
I want the output of both methods to be the same. Can anyone tell me why they are different?
P粉6429205222023-09-08 17:06:21
I will explain how these call sequences differ and how the observed behavior is possible.
I can’t tell you exactly how React updates status in batches internally, I just assume that React has complex optimizations that are irrelevant to the developer using React and require a deep understanding of React internals and maybe even changing from one version to another. (Please feel free to correct me.)
Promise.resolve()
Arranges a new microtask, which is actually equivalent to window.queueMicrotask()
.
setState
Function (possibly) will also schedule a new microtask,
Therefore their callbacks (Promise
and setState
) are called in the same execution phase.
The difference between these two variants is
handleClickA
, the setState2
hook is called between the two updater
functions, while handleClickB
, the two updater
functions will be called directly in sequence. I rewrote your code slightly to better illustrate the calling sequence:
const setState1 = setState; const setState2 = setState; const update1 = updaterFunction; // c => c + 1 const update2 = updaterFunction; // c => c + 1 const handleClickA = () => { // Scheduled functions: setState1( update1 ); // 1. --> [ update1 ] queueMicrotask(() => { // 2. --> [ update1, setState2 ] setState2( update2 ); // 4. --> [ update2 ] }); // update1(); // 3. --> [ setState2 ] // setState2( update2 ); // 4. --> [ update2 ] // update2(); // 5. --> [] }; const handleClickB = () => { // Scheduled functions: queueMicrotask(() => { // 1. --> [ setState2 ] setState2( update2 ); // 3. --> [ update2 ] }); setState1( update1 ); // 2. --> [ setState2, update1 ] // setState2( update2 ); // 3. --> [ update1, update2 ] // update1(); // 4. --> [ update2 ] // update2(); // 5. --> [] };
Here I explain the calling sequence.
(FIFO >):
handleClickA
:
// 0. --> [] - schedule update1 (setState1()) // 1. --> [ update1 ] - schedule setState2 // 2. --> [ update1, setState2 ] - invoke update1() // 3. --> [ setState2 ] - schedule update2 (setState2()) // 4. --> [ update2 ] - invoke update2() // 5. --> []
handleClickB
:
// 0. --> [] schedule setState2 // 1. --> [ setState2 ] schedule update1 (setState1()) // 2. --> [ setState2, update1 ] schedule update2 (setState2()) // 3. --> [ update1, update2 ] invoke update1() // 4. --> [ update2 ] invoke update2() // 5. --> []
I assume React attempts to batch all updater
functions currently queued.
i.e. whenever only the updater function is called, try batching them together and only update the final state once.
However, if a new setState
function is called, React may complete the current update loop and start a new render before calling the next updater periodic function. 代码>
might somehow break the batch, or
calls are made recursively, the next render will be delayed too much, or