☰
深入 React 挂载过程:ReactCompositeComponent.mountComponent 与组件实例的创建(Under-the-hood-ReactJS 源码解析)
2026/10/7 16:04:52 网站建设 项目流程
  • 教程
  • 前端
  • 文档

【免费下载链接】Under-the-hood-ReactJS

Entire 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》系列的第 3 部分,深入剖析 React 15.4.2(Stack 版本)在浏览器中执行组件挂载(Mount)的核心路径——ReactCompositeComponent.mountComponent。文章以可视化流程图(stack/images/3/part-3.svg)为线索,逐一拆解"组件树如何从上到下递归挂载"、"updater为何在挂载时才动态赋值给实例"、"自定义组件实例何时被new出来"、"componentWillMount中的setState为何不会触发重渲染"以及"首个子 VDOM 实例如何被实例化"五个关键问题。读完本文,你将掌握从ReactDOM.render到自定义组件首次被"触碰"再到子元素ReactDOMComponent挂载的完整调用链,能够独立读懂 ReactCompositeComponent.js 中对应的源码片段,并为后续理解setState更新机制(Part 9)打下基础。

回顾上下文:我们正处在挂载链路的哪一环

在进入本文之前,需要明确当前的代码坐标。整个 ReactDOM 挂载流程被拆解为 15 个部分(见 README.md 与 Intro.md),Part 3 之前我们已经完成了:

  1. 事务(Transaction)机制(Part 1):ReactDOM.render被包裹在ReactDefaultBatchingStrategyTransaction中,其close阶段会调用ReactUpdates.flushBatchedUpdates,保证挂载完成后统一校验 dirty 组件。
  2. ReactReconcileTransaction(Part 2):通过SELECTION_RESTORATION、EVENT_SUPPRESSION、ON_DOM_READY_QUEUEING三个 wrapper 保持 DOM 状态(如文本选区、事件抑制),随后ReactReconciler.mountComponent作为"平台相关逻辑的中介",把挂载任务委托给真正的组件模块。

而 Part 3 的主角,正是被ReactReconciler委托到的那位:ReactCompositeComponent.mountComponent——文档原话称它是"我们整个旅程中最大的部分之一"。

递归挂载:从 TopLevelWrapper 到第一个"真实"组件

组件树的入口:TopLevelWrapper

需要先纠正一个常见直觉:组件树中被第一个 push 进树的并不是你的应用组件,而是一个 React 内部类TopLevelWrapper。它是空的包装器,不对流程产生任何影响,因此文档建议直接跳过它,从其子组件开始观察。

这也揭示了挂载的本质规律:挂载是一棵树的深度优先递归过程——先挂载父组件,再挂载它的孩子,再挂载孩子的孩子,依次类推。文档明确断言:TopLevelWrapper挂载完成后,它的子组件(即负责管理ExampleApplication的那个ReactCompositeComponent实例)会被立即推入同一阶段继续处理。所以第 (1) 步ReactCompositeComponent.mountComponent的执行对象,就是管理ExampleApplication的内部实例。

ReactCompositeComponent.mountComponent(1) │ ├─ (2) updater = transaction.getUpdateQueue() → 赋给 inst.updater ├─ (3) this._constructComponent() → new ExampleApplication() ├─ (4) 初始挂载:componentWillMount → render └─ (5) this._instantiateReactComponent() → 为 render 返回的元素创建 VDOM 实例

为实例动态注入 updater:跨平台的关键设计

updater 是什么

ReactCompositeComponent中第一件关键动作,是把transaction.getUpdateQueue()的返回值(即ReactUpdateQueue模块)赋给组件实例的updater字段(图中步骤 (2))。

为什么要在挂载时动态赋值而不是在类定义中写死?文档给出了清晰解释:

ReactCompositeComponent是所有平台共用的类,而 updater 因平台而异,因此我们根据平台在挂载期间动态地将其赋给实例。

React 需要同时支撑浏览器(ReactDOM)、移动端(ReactNative)、服务端渲染、ReactART 等环境(见 Intro.md)。同一份ReactCompositeComponent无法内置某个平台的更新策略,于是"谁来负责把setState排队、合并、调度"这件事被推迟到挂载阶段,由当前平台的 transaction 提供。这是典型的依赖注入式设计:公共类不关心平台差异,平台差异在运行时被塞进来。

不只是 updater:props / context / refs 一同就位

文档特别强调,这一阶段为实例赋值的并不仅有updater。原文档给出了ReactCompositeComponent.js中对应的源码片段(原文档标注为#255附近):

// src/renderers/shared/stack/reconciler/ReactCompositeComponent.js#255 // 这些应该在构造方法里赋值,但是为了 // 使类的抽象更简单,我们在它之后赋值。 inst.props = publicProps; inst.context = publicContext; inst.refs = emptyObject; inst.updater = updateQueue;

也就是说,你的组件实例在挂载阶段被一次性补齐了四个核心成员:

字段来源说明
inst.propspublicProps父级传入的属性,之后可通过this.props读取
inst.contextpublicContextReact context 上下文
inst.refsemptyObject初始为空对象的 refs 集合,待子组件挂载后填充
inst.updaterupdateQueue平台相关的更新队列,setState的底层执行者

正因如此,你才可以在自己的代码里直接写this.props——这正是上述赋值语句生效的直观结果。

值得牢记:updater现在虽用不上,但它即将在setState中被使用。Stack 版本中setState的本质是把状态变更enqueue进updater(ReactUpdateQueue.enqueueSetState),再由批处理事务统一 flush。理解这一点,就为阅读 Part 9(setState 更新起点) 埋下了伏笔。

实例化你的组件:new ExampleApplication() 第一次被调用

_constructComponent 的调用链

图中步骤 (3)this._constructComponent是"我们的代码第一次被 React 生态触碰"的里程碑。经过若干构造辅助方法(文档描述为"several construction methods")后,最终执行new ExampleApplication()——此时你写在构造函数里的逻辑才第一次真正运行。

这一点对调试者意义重大:从ReactDOM.render走到这里,此前所有步骤都是 React 内部机制在运转(事务、包装、实例化内部类),直到_constructComponent才首次进入用户代码。文档对此的评价是:"Nice."——这是用户代码与 React 生态的第一次握手。

执行首次挂载:componentWillMount 与 state 的特殊处理

生命周期钩子的出场顺序

步骤 (4) 进入初始挂载(initial mount),第一个发生的行为是调用componentWillMount(仅当组件定义了该方法时)。这是全书遇到的第一个生命周期钩子。

文档同时指出componentDidMount此刻并没有被直接调用,而是被 push 进事务队列(ON_DOM_READY_QUEUEINGwrapper 的职责之一),要等所有挂载操作全部完成、事务close时才真正执行。这也解释了为什么componentDidMount总是晚于所有子组件挂载完毕——它被推迟到了挂载事务收尾阶段。

componentWillMount 里调用 setState:只算 state,不触发 render

原文档引用了官方文档的原话来佐证行为:

componentWillMount()is invoked immediately before mounting occurs. It is called beforerender(), therefore setting state in this method will not trigger a re-rendering.

随后给出了源码级验证(ReactCompositeComponent.js#476附近):

// src/renderers/shared/stack/reconciler/ReactCompositeComponent.js#476 if (inst.componentWillMount) { //.. inst.componentWillMount(); // 当挂载时,在 componentWillMount 中调用的 setState // 会设置 this._pendingStateQueue 而不触发重渲染 if (this._pendingStateQueue) { inst.state = this._processPendingState(inst.props, inst.context); } }

这里有两个值得展开的机制细节:

  1. _pendingStateQueue:componentWillMount内调用setState并不会直接改写inst.state,而是把待更新的 state 对象排入this._pendingStateQueue。
  2. _processPendingState:挂载分支随后检测到_pendingStateQueue存在,立即调用_processPendingState(inst.props, inst.context)把队列中的多个 partial state 按序合并,一次性重新计算inst.state。

为什么"不调用render也合理"?因为组件此时尚未挂载完成,立刻重渲染既无意义也无处安放结果;React 用"合并 pending state + 延后渲染"的方式,既让开发者能在componentWillMount里安全地预置初始状态,又避免了无谓的重复渲染开销。

state 重算之后:调用你的 render

state重算完成后,流程继续调用组件中开发者书写的render方法——这是"我们的代码"第二次被触碰。render返回的 JSX 描述(在我们的示例中是一个div)将成为下一步创建 VDOM 实例的输入。

创建子元素 VDOM 实例:从元素到 ReactDOMComponent

_instantiateReactComponent 的第二次登场

图中步骤 (5)this._instantiateReactComponent此前已经出现过一次(在 Part 3 之前,它曾为ExampleApplication实例化出ReactCompositeComponent)。这一次它被再次调用,目的不同:

  • 上一次:为ExampleApplication组件元素创建ReactCompositeComponent内部实例;
  • 这一次:基于render返回的元素,为它的孩子创建 VDOM 实例。

在我们的具体场景中,render返回的是div,因此其 VDOM 表示是ReactDOMComponent(注意:中文翻译版文档此处写作ReactDOMElement,英文原版为ReactDOMComponent,应以 Part-3.md 原文为准)。

再次进入 ReactReconciler.mountComponent

实例创建完成后,代码再次调用ReactReconciler.mountComponent,但这次传入的internalInstance是新创建的ReactDOMComponent实例,随后继续调用它的mountComponent……如此递归向下,挂载将持续深入直到叶子节点(真实 DOM 元素的创建在 Part 4 中展开)。

这就是整棵组件树被"从上到下、递归深入"地挂载起来的机制:每一层的mountComponent都负责"初始化自身 + 实例化自己的孩子 + 触发下一层挂载"。Part 3 的收尾阶段,控制权由此从"复合组件"(管理自定义组件的层)移交到"宿主组件"(管理真实 DOM 的层)。

流程图回顾:Part 3 的三种抽象层次

原文档在本部分结尾提供了三张递进的示意图(均在 stack/images/3/ 目录下),分别对应三种抽象层次:

  1. 完整版(part-3.svg,图 3.0):Part 3 全部调用关系的原始流程图,包含上述 (1)~(5) 全部节点;
  2. 简化版(part-3-A.svg,图 3.1)与整理版(part-3-B.svg,图 3.2):剔除冗余连线、规整排版后的核心流程;
  3. 精华版(part-3-C.svg,图 3.3):提炼出的"essential value",将直接并入全书最终的mounting总图(stack/images/6/overall-mounting-scheme.svg 所在的分支)。

图 3.0 Part 3 完整流程图(SVG 可点击放大)

小结:Part 3 的四个核心结论

  1. 递归挂载模型:挂载 = 父组件挂载 → 子组件挂载 → 孙组件挂载……TopLevelWrapper仅作入口,不产生实际影响。
  2. 实例注入时机:props、context、refs、updater四者均在挂载阶段由ReactCompositeComponent动态赋给自定义实例,其中updater(ReactUpdateQueue)是平台差异的注入点,也是未来setState的执行通道。
  3. 生命周期首秀:componentWillMount是第一个被调用的钩子;其内部setState只写入_pendingStateQueue并通过_processPendingState合并重算state,不触发render;componentDidMount被延迟到挂载事务的收尾阶段。
  4. 实例化与递归交接:_constructComponent首次执行new ExampleApplication()(用户代码首触),_instantiateReactComponent第二次被调用时为render返回的div创建ReactDOMComponent,再次经ReactReconciler.mountComponent向下递归——真正的 DOM 元素创建留待 Part 4 讲解。

至此,Part 3 的挂载核心链路已经清晰。下一步可继续阅读 Part 4(子元素挂载:props 校验与document.createElement),或返回 栈式协调器章节总览 定位自己的阅读进度。

  • 教程
  • 前端
  • 文档

【免费下载链接】Under-the-hood-ReactJS

Entire React code base explanation by visual block schemes (Stack version)

项目地址:https://gitcode.com/gh_mirrors/un/Under-the-hood-ReactJS
点击查看免费下载
上一篇:CANN/asc-devkit:设置搬运填充值函数
下一篇:超强Yi模型并行推理:多GPU分布式部署全攻略

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询