- 教程
- 前端
- 文档
【免费下载链接】Under-the-hood-ReactJS
Entire React code base explanation by visual block schemes (Stack version)
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完成自身挂载后,需要继续处理它的子节点。原文明确指出这里包含两个步骤:
this.mountChildren(挂载子组件)——真正把每个子元素实例化并挂载,这是最有技术含量的一步;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 整体挂载方案(可点击放大查看细节)
整个挂载过程可归纳为六个步骤:
- 实例化并挂载复合组件:React 为你的每个自定义组件创建
ReactCompositeComponent实例(它管理着componentWillMount等生命周期钩子),并挂载它; - 创建自定义组件实例:挂载过程中首先实例化你的组件类(此时你的
constructor会被调用); - 调用 render 产生 React 元素:执行
render(简单例子中返回div),由React.createElement创建 React 元素——可以直接调用,也可以经由 Babel 解析 JSX 后替换 render 中的标签; - 创建 DOM 组件:基于上一步的 element 对象,为
div创建ReactDOMComponent实例; - 挂载 DOM 组件:这一步真正创建 DOM 元素、注册事件监听器等;
- 处理初始子组件:为 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)
相关推荐
Agentic Awesome Skills 质量标准指南:从技能提交到 Validated 徽章的完整验证流程
Agentic Awesome Skills 质量标准指南:从技能提交到 Validated 徽章的完整验证流程 本篇指南聚焦 Agentic Awesome
教程前端文档Under-the-hood ReactJS(Stack 版)Part 6 源码解析:初始子元素挂载与递归式 Mounting 全流程
Under the hood ReactJS(Stack 版)Part 6 源码解析:初始子元素挂载与递归式 Mounting 全流程 本篇文章源自仓库系列文档
教程前端文档Under the hood ReactJS:用可视化流程图剖析 React Stack reconciler 的挂载与更新机制
Under the hood ReactJS:用可视化流程图剖析 React Stack reconciler 的挂载与更新机制 本文基于开源仓库 Under
教程前端文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考