首页 / 资讯中心 / 文章详情

React 源码解析(Stack 版)Part 9:setState 批处理更新如何被收集与冲刷

React 源码解析(Stack 版)Part 9:setState 批处理更新如何被收集与冲刷 ★ FEATURED ARTICLE
教程前端文档【免费下载链接】Under-the-hood-ReactJSEntire React code base explanation by visual block schemes (Stack version)项目地址https://gitcode.com/gh_mirrors/un/Under-the-hood-ReactJS点击查看免费下载本文是 Under-the-hood-ReactJS 系列Stack reconciler 部分第 9 篇的技术深化讲解。原文档 stack/book/Part-9.md 以可视化流程图的方式讲解了setState触发的更新是如何被 React 以“批处理batching”方式收集进dirtyComponents列表、再统一冲刷flush的完整过程。读完本文你将掌握用户事件与setTimeout两条触发路径的本质差异、ReactEventListener与ReactReconcileTransaction在批处理中的角色、合成事件与ReactErrorUtils.invokeGuardedCallback的实现细节以及setState → 开启事务 → 入列 dirtyComponents → flushBatchedUpdates这条四步链路背后的设计意图。从 Part 8 说起setState 之后发生了什么在上一章 Part 8 中我们已经看到setState之所以可用是因为组件实例继承了ReactComponent而其核心逻辑被委托给了一个名为updater的接口。在挂载阶段实例会被注入对ReactUpdateQueue的引用调用setState时实际执行的是updater.enqueueSetState(this, partialState)其内部做了两件事把传入的 partial state 对象压入内部实例的_pendingStateQueue队列每个组件持有自己的待合并状态队列之后会被逐个合并进组件状态调用enqueueUpdate将组件推入dirtyComponents列表——如果更新尚未在进行中还会先初始化更新事务再入列。而 Part 9 要回答的问题正是这些被收集进dirtyComponents的更新究竟是在什么时机、以什么方式被触发的为什么某些调用会被“批”在一起处理这是理解 ReactStack reconciler 时代更新机制的承上启下关键一环。setState 的两种触发路径用户事件与 setTimeout正如 Part 9 原文档 在流程图stack/images/9/part-9.svg中所展示的setState的调用可以由多种方式触发归纳起来可以分为两类有外部影响即用户动作如鼠标点击与无外部影响如componentDidMount中的setTimeout。为什么两种触发方式会产生差异关键在于 React 处理更新的方式是批量化的更新操作先被收集起来然后一次性冲刷flush。区别只在于“谁来启动批处理”。路径一鼠标事件驱动的批处理当鼠标事件发生时事件在顶层被ReactEventListener捕获然后经过若干层包装器wrappers的层层作用批处理更新才会被启动。这里有一个重要前提批处理只在ReactEventListener处于enabled状态时才会被触发。enabled状态与挂载阶段的联动值得一提在组件挂载过程中ReactReconcileTransaction的一个包装器会把ReactEventListener置为disabled从而让挂载过程“与世隔绝”确保挂载操作不被并发的批处理更新干扰——这是一个很聪明的防护设计挂载期间锁定事件监听挂载完成后通过事务close包装器恢复enabled状态该恢复动作在 Part 7 中已有交代ReactBrowserEventEmitter.setEnabled(previouslyEnabled)。路径二setTimeout 驱动的批处理setTimeout场景同样简单在把组件放进dirtyComponents列表之前React 会确保批处理事务已经开启opened。因此即使没有用户事件在顶层启动批处理enqueueUpdate也会先行开启事务待事务稍后关闭时统一冲刷。两条路径殊途同归要么由事件系统在顶层开启事务要么由入列逻辑兜底开启事务——最终都归结到同一个收集与冲刷模型。合成事件为了更好的开发者工具集成熟悉 React 的人都知道React 实现了“合成事件synthetic events”即包裹在原生事件之上的一层“语法糖”。合成事件在被调用后仍会尽量表现得像我们熟悉的浏览器事件。Part 9 原文引用了一段源码注释“To help development we can get better dev tool integration by simulating a real browser event”为了开发便利通过模拟一个真实的浏览器事件来获得更好的开发者工具集成这段注释对应的实现正是ReactErrorUtils.invokeGuardedCallback——它通过在一个fakeNode上派发自定义 DOM 事件间接地、有保护地调用某个回调函数var fakeNode document.createElement(react); ReactErrorUtils.invokeGuardedCallback function (name, func, a) { var boundFunc func.bind(null, a); var evtType react- name; fakeNode.addEventListener(evtType, boundFunc, false); var evt document.createEvent(Event); evt.initEvent(evtType, false, false); fakeNode.dispatchEvent(evt); fakeNode.removeEventListener(evtType, boundFunc, false); };这段代码的要点在于先创建一个内存中的fakeNodedocument.createElement(react)不挂在文档树上只作为事件派发的载体将回调函数func绑定参数后注册为react-name类型事件的监听器用document.createEventinitEvent构造一个不可冒泡、不可取消的Event再通过dispatchEvent触发派发结束后立即移除监听器避免残留。本质上它是“借 DOM 事件机制之手让被调用的回调看起来像是在真实浏览器事件中执行的”从而既保留了调用栈/调试器的友好度又为错误处理guarded callback留出了空间。setState 更新启动的四步流程回到更新本身Part 9 将setState触发更新的启动过程提炼为清晰的四步调用setState——把 partial state 压入组件自己的_pendingStateQueue如果批处理事务尚未开启则开启——保证后续入列与冲刷都在同一个事务边界内把受影响的组件加入dirtyComponents列表——同一事务内的多次setState只会让组件入列一次多个 partial state 依次排队等待合并关闭事务并调用ReactUpdates.flushBatchedUpdates——即“处理dirtyComponents中收集到的所有组件”。这四步正是整个“收集—冲刷”模型的最小闭环。值得注意的是flushBatchedUpdates并非简单地遍历列表执行一次更新事务关闭时若发现有嵌套的新更新产生例如某个组件在更新过程中又触发新的setStateReact 会再次冲刷直到dirtyComponents真正清空。这一“嵌套更新再冲刷”的细节会在下一章 Part 10 的ReactUpdatesFlushTransaction中展开其两个包装器NESTED_UPDATES与UPDATE_QUEUEING正是为对比冲刷前后dirtyComponents长度、判断是否需要再来一轮而设计的。源码级佐证文档标注的 React v15.4.2 关键位置本系列以 React v15.4.2 的 Stack reconciler 为讲解对象见 READMEPart 8、Part 9、Part 10 中陆续给出了与本章主题直接相关的源码位置整理如下以下路径为本系列文档在调试 React v15.4.2 源码时所标注的位置模块文档标注的源码路径本章相关职责ReactComponentsrc/isomorphic/modern/class/ReactComponent.jssetState方法约在第 68 行公开组件基类提供setState入口并委托给updaterReactUpdateQueuesrc/renderers/shared/stack/reconciler/ReactUpdateQueue.jsenqueueSetState入列 partial state、调用enqueueUpdateReactUpdatessrc/renderers/shared/stack/reconciler/ReactUpdates.jsflushBatchedUpdates、runBatchedUpdates、批处理事务的开启与关闭ReactReconcileTransaction挂载事务见 Part 6/7挂载期间通过包装器禁用ReactEventListener隔离挂载与更新ReactEventListener浏览器端事件顶层监听enabled状态决定批处理是否由事件系统启动需要说明的是本仓库是系列书稿与流程图仓库并未内置 React 源码本身上表路径来自本系列各章节在调试过程中记录的 React v15.4.2 源码位置可作为自行对照源码时的检索线索。从源码结构可以推断enqueueUpdate中“检查更新是否进行中”的判断正是 Part 9 所述“事务未开启则先开启”这一兜底逻辑的实现依据。收尾回顾去掉冗余后的本质Part 9 的尾声用三张递进式简化图完成了对本章的收束原始完整流程图stack/images/9/part-9.svg→ 去掉次要环节后的简化版stack/images/9/part-9-A.svg简化版再经排版整理stack/images/9/part-9-B.svg最终提炼出的“本质值”图stack/images/9/part-9-C.svg它将作为最终updating总方案的一部分被复用。从 Intro 的大全景图stack/images/intro/all-page-stack-reconciler.svg视角看整个 React 运行时只包含两个核心过程mount 与 update。Part 9 是 update 过程的起点它回答了“更新的原材料dirtyComponents是如何被收集起来的”。后续的 Part 10冲刷 dirtyComponents 的ReactUpdatesFlushTransaction、Part 1114updateComponent、children 更新等将接着回答“收集起来之后如何被逐个处理”。小结本章的核心收获可浓缩为以下几点React 的更新是批量化的先收集进dirtyComponents再由flushBatchedUpdates统一处理用户事件鼠标点击与setTimeout是两条典型的setState触发路径前者依赖ReactEventListener的enabled状态在顶层开启批处理后者由入列逻辑兜底确保事务已开启挂载事务ReactReconcileTransaction通过禁用ReactEventListener保证挂载过程不被更新打断合成事件借助ReactErrorUtils.invokeGuardedCallback模拟真实浏览器事件服务开发者工具集成setState更新启动的四步闭环调用 → 开启事务 → 入列 dirtyComponents → flushBatchedUpdates构成了整个 Stack reconciler 更新机制的地基。继续阅读上一章 Part 8this.setState 与 dirtyComponents 入列下一章 Part 10ReactUpdatesFlushTransaction 如何冲刷 dirtyComponents系列入口见 README。赞分享教程前端文档【免费下载链接】Under-the-hood-ReactJSEntire React code base explanation by visual block schemes (Stack version)项目地址https://gitcode.com/gh_mirrors/un/Under-the-hood-ReactJS点击查看免费下载相关推荐深度解析Autoware自动驾驶框架Core与Universe双版本策略全攻略深度解析Autoware自动驾驶框架Core与Universe双版本策略全攻略 Autoware作为全球领先的开源自驾框架采用CoreUniverse双版自动驾驶react-illustration-seriesReact状态更新与setState异步机制原理解析react illustration seriesReact状态更新与setState异步机制原理解析 你是否曾在React开发中遇到这样的困惑明明调用了s教程前端Windows快速免费装Office实战指南开源自动化部署工具帮你告别手动折腾Windows快速免费装Office实战指南开源自动化部署工具帮你告别手动折腾 刚换了新电脑正愁Office怎么装官网下载要登录、安装包动不动几个GB、装运维上一篇PDF4QT命令行工具详解自动化处理PDF文档的实用技巧下一篇C设计模式项目推荐23种经典模式的完整实现指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站