React18-Mikrotask-Stapelverarbeitungsproblem
P粉506963842
P粉506963842 2023-09-07 22:30:24
0
1
503

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);
};

Warum erfolgt in der React18-Version nur ein Rendering, wenn auf die handleClick方法会出现两次渲染,而点击handleClick2-Methode geklickt wird?

Ich möchte, dass die Ausgabe beider Methoden gleich ist. Kann mir jemand sagen, warum sie unterschiedlich sind?

P粉506963842
P粉506963842

Antworte allen(1)
P粉642920522

我将解释这些调用顺序有何不同,以及观察到的行为如何可能。

我无法确切地告诉你 React 内部是如何批量更新状态的, 我只是假设 React 进行了复杂的优化,这与使用 React 的开发人员无关,并且需要深入了解 React 内部,甚至可能从一个版本更改为另一个版本。 (请随时纠正我。)

区别

Promise.resolve() 安排一个新的微任务,实际上相当于 window.queueMicrotask()

setState 函数(可能)还会安排一个新的微任务, 因此它们的回调(PromisesetState)都是在同一执行阶段调用的。

这两个变体的区别在于

  • handleClickA 中,在两个 updater 函数之间调用 setState2 挂钩,而
  • handleClickB 中,两个 updater 函数都会直接依次调用。

示例代码

我稍微重写了您的代码,以更好地说明调用顺序:

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. --> []
};

呼叫顺序说明

这里我说明了调用顺序。

(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. --> []

个人解读

我假设 React 尝试对当前排队的所有 updater 函数进行批处理。

即只要仅调用更新器函数,请尝试将它们批处理在一起仅更新一次最终状态。

但是,如果调用了新的 setState 函数,React 可能会完成当前更新循环,并在调用下一个 updater 之前启动新的渲染周期代码> 函数。

我只能猜测为什么要这样做

  • 因为新的 setState 可能会以某种方式破坏批处理,或者
  • 如果递归调用新的 setState 调用,下一次渲染将会延迟太多,或者
  • React 人员仍在研究最佳优化策略及其权衡。
  • (...或者这是一个错误。)
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage