【react18原理探究实践】更新调度的完整流程

🧑‍💻 写在开头

之前写了几篇关于react的原理文章,但是后面比较忙就没有后续了,最近在准备面试,做编辑器的时候用到了自定义渲染器,在以前的时候我看过react原理的很多文章,一直想总结成自己的文章方便复习,现在又重新捡起来,继续往下写

点赞 + 收藏 === 学会 🤣🤣🤣

在使用 React 的时候,我们经常会写这样的代码:

ini 复制代码
setCount(c => c + 1);
setCount(c => c + 1);

最终结果 count 只更新一次,但值却正确地加了 2。这背后涉及到 React 的 Update 对象UpdateQueue 队列调度机制 以及 批量更新

本文就带你从源码角度完整梳理一遍 React 更新的完整流程,并解释为什么 React17 里 setTimeout/Promise 不是批量的,而 React18 却可以自动批量。


🥑 你能学到什么?

读完这篇文章,你将收获:

  • Update 对象的结构和作用
  • UpdateQueue 如何统一管理更新
  • React 更新的四个阶段:触发 → 调度 → render → commit
  • 批量更新的本质
  • 为什么 React17 里异步更新不是批量的,而 React18 变了
  • isBatchingUpdates 这个标志位到底在什么时候是 true

✍️系列文章react实现原理系列

一、Update 对象

每次调用 setStatedispatch,React 内部都会生成一个 Update 对象

js 复制代码
export type Update<State> = {
  eventTime: number,              // 事件触发时间
  lane: Lane,                     // 优先级
  tag: 0 | 1 | 2 | 3,             // 更新类型(更新/替换/强制/捕获)
  payload: any,                   // 载荷,可能是值,也可能是函数
  callback: (() => void) | null,  // 更新完成后的回调
  next: Update<State> | null,     // 指向下一个 update,形成链表
};

关键点:

  • Update 不是直接存新值,而是存一条"指令" → 最终 state 如何计算。
  • setState(1) → payload 是值
  • setState(prev => prev + 1) → payload 是函数
  • forceUpdate() → payload 为空

👉 这样 React 就能先把更新收集起来,最后统一计算。


二、UpdateQueue 更新队列

每个 Fiber 节点都有一个 updateQueue,用链表保存挂在该组件上的所有更新。

  • 多次 setState → 会依次挂多个 Update。
  • 在 render 阶段统一处理这些更新,得到新的 state。

这就解释了为什么 React 能把多个 setState 合并成一次渲染。


三、React 更新完整流程

整个更新链路可简化为 四个阶段

  1. 触发更新

    • 调用 setState,生成 update,挂到 fiber 的 updateQueue
    • 根据 lane(优先级)标记更新紧急程度
  2. 调度(Scheduler)

    • 根据优先级决定何时渲染
    • 高优先级立即执行,低优先级可能延迟
  3. render 阶段

    • 从根 fiber 构建新的 fiber 树(workInProgress)
    • 执行 processUpdateQueue → 统一应用更新队列,计算新的 state
  4. commit 阶段

    • before mutation → getSnapshotBeforeUpdate
    • mutation → 应用 DOM 变更
    • layout → 执行 useLayoutEffect / componentDidMount

最终才更新 UI。


四、批量更新(Batched Updates)

例子:

ini 复制代码
setCount(c => c + 1);
setCount(c => c + 1);

流程:

  1. 两次 setState → 生成两个 update,挂到同一个 queue。

  2. render 阶段依次应用:

    • baseState = 0
    • update1 → 1
    • update2 → 2
  3. commit 阶段一次性更新 UI

👉 结果:渲染一次,state = 2。


五、React17:为什么 setTimeout/Promise 不是批量?

React17 的批量更新依赖于 合成事件系统

  • React 分发合成事件时,用 batchedUpdates 包裹回调 → 批量更新。
  • setTimeoutPromise.then 属于原生调度,React 无法感知,不会进入 batchedUpdates

所以在 React17 里:

scss 复制代码
setTimeout(() => {
  setCount(c => c + 1); // render 一次
  setCount(c => c + 1); // render 再一次
}, 0);

👉 会渲染两次。


六、React18:自动批量更新

React18 引入 Automatic Batching

  • 不管 React 事件、setTimeout、Promise、async/await...
  • 只要在同一个任务里触发的更新 → 自动批量合并。
ini 复制代码
setTimeout(() => {
  setCount(c => c + 1);
  setCount(c => c + 1);
}, 0);

👉 React18 里只渲染一次,最终 state = 2。

需要立即更新时,可以用 flushSync

javascript 复制代码
import { flushSync } from 'react-dom';

flushSync(() => {
  setCount(c => c + 1); // 立即更新
});

七、isBatchingUpdates = true 的时机

isBatchingUpdates 决定 React 是否开启批量更新:

  • true → setState 只入队,不立即渲染
  • false → setState 立即触发渲染

React17

  • 合成事件回调、生命周期 → true
  • 原生事件 / setTimeout / Promise → false

React18

  • 自动批量更新 → 所有场景默认 true
  • 除非 flushSync 强制同步

源码简化版:

ini 复制代码
function batchedUpdates(fn, ...args) {
  const prev = isBatchingUpdates;
  isBatchingUpdates = true;
  try {
    fn(...args);
  } finally {
    isBatchingUpdates = prev;
    if (!isBatchingUpdates) flushUpdates();
  }
}

✅ 总结

  1. React 更新由 Update + UpdateQueue 驱动。
  2. render 阶段统一计算 state,commit 阶段更新 DOM。
  3. 批量更新合并多个 setState,只渲染一次。
  4. React17:只有合成事件是批量的,异步回调不是。
  5. React18:自动批量更新,几乎所有场景都合并。
  6. isBatchingUpdates 是批量开关,18 里几乎全局开启。

一句话概括:

React 更新就是 收集更新 → 按优先级调度 → render 阶段合并计算 → commit 阶段一次性更新

React18 之后,批量更新从「合成事件级」升级为「全局级」。


相关推荐
oak隔壁找我几秒前
JavaScript 模块化演进历程:问题与解决方案。
前端·javascript·架构
Elieal14 分钟前
AJAX 知识
前端·ajax·okhttp
sulikey33 分钟前
Qt 入门简洁笔记:从框架概念到开发环境搭建
开发语言·前端·c++·qt·前端框架·visual studio·qt框架
烛阴1 小时前
循环背后的魔法:Lua 迭代器深度解析
前端·lua
元拓数智1 小时前
现代前端状态管理深度剖析:从单一数据源到分布式状态
前端·1024程序员节
mapbar_front1 小时前
Electron 应用自动更新方案:electron-updater 完整指南
前端·javascript·electron
天一生水water2 小时前
three.js加载三维GLB文件,查看三维模型
前端·1024程序员节
无风听海2 小时前
HarmonyOS之启动应用内的UIAbility组件
前端·华为·harmonyos
冰夏之夜影2 小时前
【科普】Edge出问题后如何恢复出厂设置
前端·edge
葱头的故事3 小时前
vant van-uploader上传file文件;回显时使用imageId拼接路径
前端·1024程序员节