You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React18中handleClick与handleClick2渲染次数差异原因咨询

React18中handleClick与handleClick2的渲染差异解析

代码示例

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

问题

在React18版本中,为何点击handleClick方法会触发两次渲染,而点击handleClick2方法仅触发一次渲染?原本预期两种方法的执行结果一致,二者存在差异的原因是什么?

核心原因:微任务队列顺序与React批处理机制的交互

React18的自动批处理会将同一上下文内的多个状态更新合并为一次渲染,但这一机制的效果会受浏览器事件循环中微任务的执行顺序影响:

1. handleClick的执行流程

  • 同步调用setC1((c) => c + 1):React将该更新加入待处理队列,并调度一个微任务(记为微任务A)来处理更新。
  • 调用Promise.resolve().then(...):将回调函数加入微任务队列(记为微任务B),此时微任务队列的顺序是微任务A在前,微任务B在后。
  • 点击事件的宏任务执行完毕后,浏览器按顺序执行微任务:
    • 先执行微任务A:React处理队列中的第一个更新,触发第一次渲染。
    • 再执行微任务B:调用第二个setC1,此时没有活跃的批处理上下文,React直接处理该更新,触发第二次渲染。

2. handleClick2的执行流程

  • 调用Promise.resolve().then(...):将回调函数加入微任务队列(记为微任务B)。
  • 同步调用setC1((c) => c + 1):React将该更新加入待处理队列,并调度一个微任务(记为微任务A),此时微任务队列的顺序是微任务B在前,微任务A在后。
  • 点击事件的宏任务执行完毕后,浏览器按顺序执行微任务:
    • 先执行微任务B:调用第二个setC1,该更新被加入同一个待处理队列。
    • 再执行微任务A:React处理队列中的两个更新(由于是函数式更新,会合并计算为c + 2),仅触发一次渲染。

关键总结

两种方法的差异本质是微任务的注册顺序不同,导致后续的状态更新是否能被纳入同一个批处理队列:

  • handleClick中,同步更新的调度微任务先进入队列,导致两次更新被分别处理,触发两次渲染。
  • handleClick2中,Promise回调的微任务先进入队列,使得两次更新被纳入同一个批处理队列,最终合并为一次渲染。

内容的提问来源于stack exchange,提问作者1 2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 17:55:03