拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Under-the-hood-ReactJS 入门导读:以流程图驱动 React Stack 协调器源码调试

Under-the-hood-ReactJS 入门导读:以流程图驱动 React Stack 协调器源码调试
  • 教程
  • 前端
  • 文档

【免费下载链接】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》仓库中文版的开篇导读,主题是带你理解 React(Stack 版本,对应 React v15.4.2)内部架构的两条核心主线——组件的挂载(mount)与更新(update)。它向你展示如何借助作者逐行调试生成的“整体流程图”快速建立全局认知,并给出一个可复现的最小调试示例,让你从ReactDOM.render与component.setState这两个入口出发,沿着 15 个章节逐步深入源码。读完本文,你将掌握流程图的阅读方法(模块颜色图例、多平台依赖关系)、搭建本地调试代码的方法,以及理解 React 如何把 JSX 一步步转化为真实 DOM 的整体框架。

本仓库的所有流程图均可点击后在新标签页打开并放大阅读。建议将文章与当前讲解的流程图分别放在两个窗口(标签页)中,便于对照文本与调用链。仓库说明:整个 Stack 协调器流程图被划分为 15 个部分(Part 0 ~ Part 14),本导读是这趟源码之旅的起点,目录见 README.md。

整体流程图:两条主流程、一次全局俯瞰

仓库在 stack/images/intro/all-page-stack-reconciler-25-scale.svg 提供了一张覆盖全部源码调用关系的总览图(缩略图见同目录的all-page-stack-reconciler-25-scale.jpg)。先花点时间通览整张图——它看起来非常复杂,但实际上只描述了两个流程:

  • 挂载(mount):把组件树渲染成真实 DOM 并插入文档;
  • 更新(update):响应setState等变更,重新渲染受影响的部分。

图例刻意跳过了卸载(unmount),因为卸载本质上是“反向挂载”,删除它可以让流程图大幅简化。作者明确说明:这张图并非与源码 100% 对应,它只描述架构的主要部分,大约覆盖源码的 60%;另外约 40% 的代码因不具备视觉价值而被省略。因此,阅读时应把流程图当作架构索引而非逐行真相。

读懂图中的颜色:父模块着色规则

乍看之下,图中有大量颜色。其实规则很简单:每个逻辑项(图上的形状)都以其父模块的颜色高亮。例如,若红色模块 B调用了方法 A,那么方法 A也是红色的。由此你可以一眼看出某个方法隶属于哪个模块、被哪条调用链驱动。

模块颜色与文件路径的完整图例见 stack/images/intro/modules-src-path.svg,其中标注了每个模块对应的源码文件路径(例如ReactMount、ReactCompositeComponent、ReactDOMComponent等)。将这些模块放入一张图,就得到了模块之间的依赖关系图:stack/images/intro/files-scheme.svg。

多平台支持:同一个模块,多份实现

React 生来就是为多种运行环境设计的:

  • 移动端(ReactNative)
  • 浏览器(ReactDOM)
  • 服务端渲染
  • ReactART(使用 React 绘制矢量图形)
  • 其它

因此,实际源码中不少文件比单平台依赖图看起来“更大”——每个平台都有独立实现。完整版多平台依赖关系见 stack/images/intro/modules-per-platform-scheme.svg:图中有些项“翻倍”出现,正说明它们为每个平台各提供了一份实现。

以ReactEventListener为例,不同平台的事件监听实现显然不同。这些平台相关的模块需要以某种方式注入当前逻辑流程,React 内部确实存在大量此类“注入器”,其用法属于标准的组合(composition)模式。为保持图面简洁,作者选择忽略它们。本篇系列选定常规浏览器中的 React DOM作为讲解平台——它是最常用的平台,且能完整覆盖 React 的所有架构设计理念。

调试示例:两个入口、一个最小组件

学习框架/库源码的最佳方式就是研读并调试源码。本系列将调试两个核心流程:

  • ReactDOM.render—— 对应组件的挂载;
  • component.setState—— 对应组件的更新。

为此,准备一个简单、易调试的最小示例:包含一个子组件和一个父组件,父组件中预留了几乎全部生命周期钩子与setState触发点(默认被注释掉,可自行打开用于调试更新流程)。

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() {} );

这段代码刻意保持“小而全”:既有组件嵌套(ExampleApplication与ChildCmp)、属性传递(childMessage),又保留了生命周期钩子的空实现与两处注释掉的setState触发方式(setTimeout定时触发与按钮点击触发)。在调试时,你可以在这些断点处逐步跟踪挂载与更新两条流程。官方文档中,这些生命周期方法分别对应挂载期(componentWillMount/componentDidMount)、更新期(shouldComponentUpdate/componentDidUpdate/componentWillReceiveProps)和卸载期(componentWillUnmount)。

两条主流程的源码级预览

本导读只给出骨架,后续 Part 0 ~ Part 14 将逐块拆解整个流程图。这里先预告几个关键环节,便于建立整体心智模型。

挂载流程的起点

挂载流程从ReactDOM.render进入(详见 Part 0):JSX 首先被转换为结构简单的React 元素(element);接着进入ReactMount模块——ReactDOM内部几乎没有逻辑,它只是与ReactMount协作的接口,调用ReactDOM.render实际等于调用ReactMount.render。挂载的本质是把组件的高层描述(组件、属性、事件监听、嵌套)逐步降级为低层数据(HTML),从而插入文档。

值得注意的是,React 总是从内部类TopLevelWrapper开始渲染组件树,其render方法仅返回this.props.child(源码见src\renderers\dom\client\ReactMount.js#277)。虚拟 DOM 在 React 源码中并没有名为 “Virtual DOM” 的文件或类——它只是一个概念,具体由ReactCompositeComponent(自定义组件)、ReactDOMComponent(HTML 标签)和ReactDOMTextComponent(文本节点)三类内部组件承担,相关转换见 stack/images/0/jsx-to-vdom.svg。

Transaction 与批量更新

React 以**批量(batch)**方式执行更新:先收集操作,再统一执行,从而让前置/后置条件只对整批元素应用一次。支撑这一机制的是Transaction(事务)模式(详见 Part 1):每个 wrapper 是一对initialize/close方法,事务依次执行initialize(缓存返回值)→ 事务方法本身 →close。React 内部存在多种事务类,例如:

  • ReactDefaultBatchingStrategyTransaction:wrapper 中的close调用ReactUpdates.flushBatchedUpdates,启动对脏组件的校验与重渲染;
  • ReactReconcileTransaction:wrapper 用于保存选中文本范围、抑制 blur/focus 事件、收集componentDidUpdate回调(见 Part 2)。

事务的其它典型用途还包括:在 reconciliation 前后保留输入选区、在重排 DOM 时停用事件系统、在 worker 线程完成 reconciliation 后把 DOM 变更刷回主线程等。

组件的实际挂载

ReactCompositeComponent.mountComponent是挂载旅程中最重的部分之一(详见 Part 3)。挂载期间,组件实例会被赋予props、context、refs以及updater(来自ReactUpdateQueue,是后续setState的关键依赖,源码见src\renderers\shared\stack\reconciler\ReactCompositeComponent.js#255)。随后new ExampleApplication()才真正被创建——这是你的代码首次被 React 生态触碰。接着调用componentWillMount(在此调用setState只重算 state 而不会触发 render,因为组件尚未挂载),再调用render()得到子元素,为子元素创建ReactDOMComponent等 VDOM 实例并继续递归挂载。

挂载到 HTML 标签层时(详见 Part 4),若标签是video、form、textarea等“复杂标签”,会套用额外 wrapper(如ReactDOMSelect、ReactDOMTextarea,位于src\renderers\dom\client\wrappers\);随后校验内部 props(例如dangerouslySetInnerHTML必须形如{__html: ...}),最终由document.createElement创建真实 DOM 元素。

更新流程的起点

setState的触发有两种方式:外部事件(如鼠标点击)或内部代码(如setTimeout,详见 Part 9)。前者经ReactEventListener顶层处理后开启批量更新(仅当其enabled时;挂载期ReactReconcileTransaction的 wrapper 会暂时禁用它以保证挂载安全);后者则在把组件放入dirtyComponents列表前确保事务已开启。两条路径最终汇合为同一套步骤:调用setState→ 若事务未开启则开启 → 把受影响的组件加入dirtyComponents→ 关闭事务并调用ReactUpdates.flushBatchedUpdates统一处理。整体示意见 stack/images/9/set-state-update-start.svg。

下一站

至此,我们已经掌握了本系列的全局框架:一张总流程图、两类核心流程(挂载/更新)、一套模块着色规则与多平台依赖视角,以及一个可用于调试的最小示例。接下来就可以从ReactDOM.render的入口出发,一个部分接一个部分地拆解整张流程图。请继续阅读:

  • 第 0 部分:ReactDOM.render 与挂载起点
  • 完整章节目录:中文版首页

如需对照英文原文或其它语言版本,可访问 英文版 Intro 与 韩文版;Fiber 版本的内容仍在建设中,见 fiber/book/Intro.md。

  • 教程
  • 前端
  • 文档

【免费下载链接】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
点击查看免费下载
上一篇:Lucky DDNS Webhook 实战指南:公网 IP 一变,手机立刻收到推送
下一篇:rclone copyurl 命令详解:免落盘直传 URL 文件到任意远程存储

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

返回列表