☰
React Stack Reconciler 挂载流程解析(第 6 部分):mountChildren 与初始子组件挂载全链路
2026/10/7 9:43:53 网站建设 项目流程
  • 教程
  • 前端
  • 文档

【免费下载链接】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
点击查看免费下载

React 的挂载(mounting)并不是只创建根组件就结束,真正的重头戏在于把render返回的元素递归地铺开成一棵真实的 DOM 树。本篇基于 Under-the-hood-ReactJS 仓库 Stack 版系列的 Part 6,深入剖析子组件挂载的两大步骤(this.mountChildren与DOMLazyTree.queueChild)、ReactMultiChild/ReactChildReconciler的分工,以及完整回顾从ReactCompositeComponent到ReactDOMComponent的递归挂载链路。读完本文,你将能清晰回答"React 到底如何把一个组件树变成 DOM 树、子组件是按什么规则分派给不同内部组件类"这两个核心问题。

一、Part 6 在整个挂载流程中的位置

在进入子组件挂载之前,需要先回顾整个系列的脉络:

  • Part 0 讲解了ReactDOM.render入口:JSX 被 Babel 解析后通过React.createElement变成 React 元素,再被实例化为三种内部组件类型——ReactCompositeComponent(自定义组件)、ReactDOMComponent(HTML 标签)、ReactDOMTextComponent(文本节点);
  • Part 2 引入了ReactReconcileTransaction事务及其三个 wrapper,以及ReactReconciler这个"中介者"模块;
  • Part 3 展开了ReactCompositeComponent.mountComponent:实例化你的自定义组件、调用生命周期钩子、执行render,然后为返回的元素创建ReactDOMComponent实例并继续调用mountComponent;
  • Part 4 与 Part 5 分别覆盖了ReactDOMComponent的标签包装、props 校验、document.createElement建真实 DOM 节点,以及updateDOMProperties对 props 的 diff 应用。

到 Part 6 为止,div这个元素本身的挂载已经完成,接下来顺理成章的问题就是:它的 children 怎么办?这正是本篇的核心主题。

二、子组件挂载的两个步骤:mountChildren 与 queueChild

在ReactDOMComponent完成自身挂载后,需要继续处理它的子节点。原文明确指出这里包含两个步骤:

  1. this.mountChildren(挂载子组件)——真正把每个子元素实例化并挂载,这是最有技术含量的一步;
  2. DOMLazyTree.queueChild(连接父子)——把挂载好的子节点挂接到父节点上。

DOMLazyTree是一个工具类,负责维护挂载过程中构建的树形数据结构(例如它会让children与node(真实 DOM 节点)保持关联)。之所以叫 "Lazy",与整个挂载流程"先把数据准备好、再一次性插入文档"的延迟策略一致——这一点在 Part 7 的parentNode.insertBefore(tree.node)收尾步骤中可以看到最终落地效果。

从模块分工上看,子组件管理被独立抽成了一个专门的模块:ReactMultiChild(书中给出的路径为src\renderers\shared\stack\reconciler\ReactMultiChild.js)。这个模块的核心职责就是统一处理"一个父组件的一批子组件"的挂载与后续更新(它同时服务于 mount 和 update 两条链路)。

三、mountChildren 内部的两个主要任务

mountChildren方法内部同样包含两个主要任务:

1. 实例化子组件(交给 ReactChildReconciler)

使用ReactChildReconciler来实例化所有的子组件。这一层负责处理 children 的各种形态:数组、单个元素、文本字符串等,并把它们统一转换为内部组件实例。React 元素的 key/ref 等元数据也会在这一层被妥善处理,为后续的 diff 更新(Part 8 之后的更新流程)做好准备。

2. 挂载子组件

实例化完成后,逐个调用挂载逻辑。这里的关键点在于挂载流程完全取决于子组件的类型,而类型分派规则非常清晰:

子组件来源实例化的内部组件类对应挂载流程
简单 HTML 标签(如<div>、<span>)ReactDOMComponent走 DOM 组件挂载(Part 4 / Part 5 的流程)
自定义组件(如<ChildCmp>)ReactCompositeComponent走复合组件挂载(Part 3 的流程)

换句话说,Part 6 并不是一条全新的代码路径,而是一个分叉点:它对每个子元素重新进入前面已经讲过的挂载过程。原文用一句非常精炼的话概括了这种递归性——"The mounting flow, again, depends on what the child type is."

这与 Part 0 里提到的三种内部组件类型是一一对应的:JSX → React 元素 →(实例化为)ReactCompositeComponent/ReactDOMComponent/ReactDOMTextComponent。Part 6 正是这套分类机制在"子组件"维度上的实际执行。

四、整体挂载流程:六步回顾

为了让读者不被细节淹没,Part 6 特意插入了一次"One more time"的整体回顾,用下面这张总图把前面的内容串起来:

图 4.1 整体挂载方案(可点击放大查看细节)

整个挂载过程可归纳为六个步骤:

  1. 实例化并挂载复合组件:React 为你的每个自定义组件创建ReactCompositeComponent实例(它管理着componentWillMount等生命周期钩子),并挂载它;
  2. 创建自定义组件实例:挂载过程中首先实例化你的组件类(此时你的constructor会被调用);
  3. 调用 render 产生 React 元素:执行render(简单例子中返回div),由React.createElement创建 React 元素——可以直接调用,也可以经由 Babel 解析 JSX 后替换 render 中的标签;
  4. 创建 DOM 组件:基于上一步的 element 对象,为div创建ReactDOMComponent实例;
  5. 挂载 DOM 组件:这一步真正创建 DOM 元素、注册事件监听器等;
  6. 处理初始子组件:为 DOM 组件的直接子元素创建实例并挂载。根据每个子元素是自定义组件还是普通 HTML 标签,递归回到步骤 1 或步骤 5,然后对所有嵌套元素重复这一过程。

这套递归逻辑正是"挂载一棵树"的完整定义:先父后子、层层递进,直到整棵虚拟 DOM 树都被物化为真实 DOM 节点。原文的评价是 "It's pretty straightforward"——确实,抽象为递归后,结构非常清晰。

五、componentDidMount 的入队时机

六个步骤全部执行完毕,挂载基本完成。此时生命周期钩子componentDidMount并不会被立即调用,而是入队等待事务的 close 阶段触发。

这一点与 Part 2 讲解的ReactReconcileTransaction三个 wrapper(SELECTION_RESTORATION、EVENT_SUPPRESSION、ON_DOM_READY_QUEUEING)一脉相承:componentDidMount的调用回调会被推入transaction.reactMountReady队列,只有当事务真正 close(即所有挂载副作用都完成、DOM 已就绪)时才会被notifyAll触发。完整收尾在 Part 7 中有详细展示:先parentNode.insertBefore(tree.node)把节点插入文档,再由 close wrapper 通知所有回调。这也解释了为什么componentDidMount里可以安全地访问 DOM——因为此时节点已真正在文档里了。

六、从完整流程图到核心价值提炼

Part 6 的收尾部分遵循了整个系列的固定节奏:从完整流程图逐步做减法,提炼出最终可复用的核心价值。

图 6.1 Part 6 完整流程(可点击放大查看细节)

首先是简化版本(移除冗余且不重要的分支后得到 part-6-A),然后是整理间距与对齐的 refactor 版本(part-6-B),最后沉淀出可并入最终 mounting 总图的核心价值版:

图 6.2 Part 6 核心价值(可点击放大查看细节)

整个系列的mounting最终方案图(mounting-parts-C.svg)正是由各 Part 的 C 图拼接而成的——Part 6 贡献的正是"子组件挂载 + 递归回到复合组件/DOM 组件挂载"这一段。

七、小结与延伸阅读

Part 6 的核心结论可以浓缩为一张心智模型:

挂载子组件 =ReactChildReconciler实例化子组件(按类型分派为ReactDOMComponent或ReactCompositeComponent)+ 递归挂载 +DOMLazyTree.queueChild连接父子。

到这里,mount 链路还剩最后一步:把构建好的树真正插入文档,并关闭事务触发componentDidMount——这正是 Part 7 的内容。对于想继续深入本系列的读者,推荐按以下顺序阅读:

  • Part 5:子组件挂载之前,父元素自身的updateDOMProperties流程;
  • Part 7:挂载收尾——DOM 插入与事务 close;
  • Part 0:JSX 到三种内部组件类型的转换规则,理解"分派"的源头;
  • Intro:整个 Stack reconciler 总览图与调试用的示例组件代码(ExampleApplication/ChildCmp)。

仓库还提供了中文版 Part 6 与韩文版 Part 6 可供对照阅读。

  • 教程
  • 前端
  • 文档

【免费下载链接】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
点击查看免费下载
上一篇:Gumroad 仓库 Sidekiq 实战指南:作业去重、批量清理与长任务队列编排
下一篇:Serena 的 Memories 系统怎么用:mem 引用、onboarding 与 memories check

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

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

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

立即咨询