- 教程
- 前端
- 文档
【免费下载链接】Under-the-hood-ReactJS
Entire React code base explanation by visual block schemes (Stack version)
导读
本篇是《Under-the-hood-ReactJS》(React 源码可视化块状流程解读系列)中 Stack 协调器部分的第 0 部分深度解析。文章围绕 React v15.4.2 的ReactDOM.render调用链路展开,厘清"JSX 到 React 元素"的转换、ReactMount模块的挂载职责、TopLevelWrapper的实例化顺序,以及虚拟 DOM 背后真实的内部组件类型(ReactCompositeComponent、ReactDOMComponent、ReactDOMTextComponent)等核心概念。读完后你将能准确回答"调用ReactDOM.render之后的第一秒发生了什么",并掌握如何沿着ReactElement.js、ReactMount.js等源码路径自行验证每一步,为后续学习事务与批量更新(第 1 部分)打下坚实基础。
1. 本部分在全系列中的位置与阅读方式
本仓库以两套协调器为主线:《Under-the-hood-ReactJS》Stack 版(React v15.4.2)与 Fiber 版(React v16+),本部分属于Stack 协调器的讲解体系。整个挂载/更新流程被作者拆解为 15 个部分,每一部分对应一张可点击放大的可视化流程图,本部分(Part 0)正是整个调用链的第一站。总览流程图位于 stack/images/intro/all-page-stack-reconciler-25-scale.svg(同名 JPG 预览见 all-page-stack-reconciler-25-scale.jpg),中文版章节目录见 stack/languages/chinese/book/README.md。
该系列在 README.md 中明确说明:本系列基于React v15.4.2解释"旧版 React"(Stack 协调器)的工作原理。因此文中所引用的src\isomorphic\classic\element\ReactElement.js、src\renderers\dom\client\ReactMount.js等路径均为 React 官方源码相对路径,指向的是 React v15.4.2 的代码结构,而非本仓库内文件——本仓库只存放图文解析。阅读建议:每张流程图可点击在新标签页打开并缩放查看,建议将文章与对应流程图分别放在两个窗口(标签页)中对照阅读。
2. 入口点:ReactDOM.render 与 JSX 到 React 元素
2.1 一切从这里开始
ReactDOM.render是整个应用的渲染入口,我们的应用程序正是从这里开始被渲染到 DOM 中。为便于调试,作者在系列介绍部分创建了一个简单的<ExampleApplication />组件作为调试目标,完整代码见 stack/languages/chinese/book/Intro.md,其核心结构如下:
class ChildCmp extends React.Component { render() { return <div> {this.props.childMessage} </div> } } class ExampleApplication extends React.Component { constructor(props) { super(props); this.state = {message: 'no message'}; } componentWillMount() { /* ... */ } componentDidMount() { /* setTimeout(() => { this.setState({ message: 'timeout state message' }); }, 1000); */ } shouldComponentUpdate(nextProps, nextState, nextContext) { return true; } componentDidUpdate(prevProps, prevState, prevContext) { /* ... */ } componentWillReceiveProps(nextProps) { /* ... */ } componentWillUnmount() { /* ... */ } onClickHandler() { /* this.setState({ message: 'click state message' }); */ } render() { return <div> <button onClick={this.onClickHandler.bind(this)}> set state button </button> <ChildCmp childMessage={this.state.message} /> And some text as well! </div> } } ReactDOM.render( <ExampleApplication hello={'world'} />, document.getElementById('container'), function() {} );2.2 第一步:JSX 被转换成 React 元素(Element)
调用ReactDOM.render后发生的第一件事是:JSX 会被转换成 React 元素(Element)。React 元素是简单、直白的普通对象,具有简单的结构,它们仅仅展示"从本组件渲染中返回的内容",除此之外没有别的职责。在 React v15 中,JSX 语法经编译后会变成React.createElement(type, props, ...children)的调用,最终产出的元素对象主要包含以下字段:
| 字段 | 含义 |
|---|---|
type | 由 JSX 描述的标记对象:可以是组件类(如ExampleApplication),也可以是 HTML 标签的字符串(如'button') |
props | 从父组件传入的属性集合(含children) |
key | 用于列表 diff 时识别项的唯一标识 |
ref | 用于在代码中访问组件实例或真实 DOM 节点 |
另外,在 React 组件创建过程中,它还会将defaultProps与props合并(如果显式声明了defaultProps),并验证propTypes。也就是说,元素对象的产出并不是"直通"的,中间经过了默认属性合并与类型校验这两道关卡。
该逻辑的实现细节可参考源码(React v15.4.2 仓库路径):src\isomorphic\classic\element\ReactElement.js。如需在本系列中对比查看作者对该文件的解读脉络,可回到 介绍章节。
本部分对应的整体流程图如下(图 0-0,即 Part 0 的完整调用链):
3. ReactMount:挂载逻辑的真正归属
3.1 ReactDOM 只是一个轻量接口
在流程图上你可以看到一个名为ReactMount(01)的模块,它包含组件挂载的全部逻辑。实际上,ReactDOM本身没有任何逻辑,它只是一个与ReactMount配合使用的接口。所以当你调用ReactDOM.render时,真正执行的是ReactMount.render。
那"挂载"(mounting)究竟指的是什么?源码注释给出了权威定义:
挂载是初始化 React 组件的过程。该过程通过创建组件所代表的 DOM 元素,并将它们插入到提供的
container中来实现。
3.2 挂载的实质:高层描述 → 低层数据
用更通俗的语言来理解,挂载过程可以想象成下面的转换(图 0-1 / 图 0-2,JSX 到 HTML):
React 需要将你的组件描述转换为 HTML以将其放入 DOM 中。要做到这一点,它必须处理所有的属性、事件监听、内嵌组件和组件逻辑,把"高阶描述"(组件)转换成真正可以放进网页的"低阶数据"(HTML)。这就是挂载过程的实质。
与挂载密切相关的另一张细化图(小尺寸版)可查看 mounting-scheme-1-small.svg,它更精简地展示了"组件描述 → 真实 HTML"的对应关系。
3.3 有趣的事实:滚动监听与重排优化(02)
在探索挂载流程的过程中,作者插入了一个有趣的事实:在第一次渲染根组件时,React 会初始化滚动监听并缓存滚动值,以便应用程序代码可以访问它们而不触发重排(reflow)。
原因是:由于浏览器渲染机制的不同,一些 DOM 值不是静态的,每次在代码中使用它们时都会被重新计算,这显然会影响性能。实际上,这主要影响的是不支持pageX和pageY的旧版浏览器,React 试图对这种情况做优化。这个小例子说明:制作一个运行快速的工具需要用到很多底层技术,滚动值的缓存就是其中一例。
4. 实例化 React 组件:TopLevelWrapper 才是第一个实例
4.1 流程图(03)处实例化的并不是我们的组件
回到流程图,在图中(03)处标明"创建实例"。你可能会以为这里创建的是<ExampleApplication />的实例,但实际上为时过早——此处实例化的是TopLevelWrapper(一个 React 内部的类)。
React 总是从TopLevelWrapper开始渲染一棵组件树。它几乎是一个空的包装器,其render方法(组件的 render)随后将返回<ExampleApplication />:
// src\renderers\dom\client\ReactMount.js#277 TopLevelWrapper.prototype.render = function () { return this.props.child; };也就是说,目前被创建的只有TopLevelWrapper,真正的ExampleApplication实例要在后续的挂载递归中才会被创建。
4.2 三种内部组件类型与虚拟 DOM
请观察下面的流程图(图 0-3,JSX 到虚拟 DOM):
可以看到,图中分为三个部分:JSX 会被转换成 React 内部三种组件类型中的一种:
ReactCompositeComponent—— 代表我们自定义的组件(如ExampleApplication);ReactDOMComponent—— 代表 HTML 标签(如<div>、<button>);ReactDOMTextComponent—— 代表文本节点。
其中ReactDOMTextComponent在本书中被略过,重点讲解前两类。
那么"内部组件"与虚拟 DOM是什么关系?虚拟 DOM 是一种 DOM 的表现形式,React 用虚拟 DOM 进行组件差异计算(diff)等过程,过程中无需直接操作真实 DOM,这使得 React 在更新视图时更快。但注意一个容易被忽略的事实:在 React 的源码中没有名为 "Virtual DOM" 的文件或类。虚拟 DOM 只是一个概念,一种操作真实 DOM 的方法论。因此,作者明确表达了自己的观点:虚拟 DOM 不是单个 React 组件,而是这三个内部组件类的集合——ReactCompositeComponent、ReactDOMComponent和ReactDOMTextComponent。后续章节(第 2 部分及以后)会进一步解释为什么这种划分成立。
4.3 有趣的事实:DOM 嵌套验证(validateDOMNesting)
在探索中还值得注意一个验证环节:几乎每次内嵌组件渲染时,都会被一个专门用于 HTML 验证的validateDOMNesting模块验证。DOM 内嵌验证指的是子标签 -> 父标签的标签层级验证。
例如,如果父标签是<select>,那么子标签应该是以下之一:option、optgroup或者#text。这些规则定义在 HTML 规范(https://html.spec.whatwg.org/multipage/syntax.html)中。你可能已经见过这个模块的工作方式,它通常像这样报错:
<div> cannot appear as a descendant of <p>(<div>不能作为<p>的后代出现)
5. 小结:Part 0 的要点提炼
让我们回顾一下本部分的内容。先看完整流程图,删除掉多余的不太重要的部分,变成 图 0-4(简述版);再调整间距和对齐,得到 图 0-5(调整版)。实际上,这就是本部分发生的所有事情。
由此,我们可以提取出第 0 部分的核心要点,并将其用于最终的mounting(挂载)流程中:
一句话概括本部分:调用ReactDOM.render→ JSX 被转换为 React 元素 → 经由ReactMount.render进入挂载流程 → 在事务(Transaction)的保护下实例化TopLevelWrapper→ 组件树被递归转换为三种内部组件类型(复合组件、DOM 组件、文本组件)并最终产出可插入文档的 HTML。
不过请注意,挂载方法真正执行时是被包裹在事务中的——在 mount 之后,React 需要检查已挂载的组件对环境产生的影响并执行相应更新。这正是第 1 部分(事务与批量更新)要深入讲解的内容:那里会介绍ReactUpdates模块、ReactDefaultBatchingStrategyTransaction及其包装器(FLUSH_BATCHED_UPDATES、RESET_BATCHED_UPDATES)。
6. 系列导航
- << 上一页:介绍(含示例应用与总览流程图)
- 下一页:第 1 部分 >>(事务 Transaction)
- 中文版首页(章节总目录)
- 仓库根 README(含两套协调器结构总览)
- 教程
- 前端
- 文档
【免费下载链接】Under-the-hood-ReactJS
Entire React code base explanation by visual block schemes (Stack version)
相关推荐
Under-the-hood-ReactJS 源码解析:从 ReactDOM.render 到挂载的完整调用链(Stack Reconciler 第 0 部分)
Under the hood ReactJS 源码解析:从 ReactDOM.render 到挂载的完整调用链(Stack Reconciler 第 0 部分)
教程前端文档Under-the-hood-ReactJS 源码解析(Part 0):从 ReactDOM.render 到挂载,JSX 如何一步步变成真实 DOM
Under the hood ReactJS 源码解析(Part 0):从 ReactDOM.render 到挂载,JSX 如何一步步变成真实 DOM 本文聚焦
教程前端文档3种方式搞定One API版本升级:从手动到智能的完整方案
3种方式搞定One API版本升级:从手动到智能的完整方案 你是否还在为AI接口管理系统的版本更新而烦恼?每次升级都要备份数据、替换文件、重启服务,还要担心兼容
后端API网关LLM 网关大模型
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考