[FE] React 初窥门径(四):React 组件的加载过程(render 阶段)

1. 回顾

前几篇文章中,我们采用了 VSCode 插件 CodeTour 来记录代码的执行过程,

并把相关的数据 .tour/ 放到了 github: thzt/react-tour 中。

截止到本文为之,我们总共记录了这些 code-tour,

复制代码
.tour/
├── 2. 构建过程.tour
├── 3.1 react 的加载过程.tour
├── 3.2 react-dom 的加载过程.tour
├── 4.1 组件加载过程:函数组件(call stack).tour
├── 4.1.1 组件加载过程:函数组件(全流程).tour
└── 4.2 组件加载过程:类组件(call stack).tour

本文重点介绍 4.1.1 组件加载过程:函数组件(全流程) 相关的内容。

2. 极简的示例项目

现在我们开始介绍 React 函数组件的加载全流程,我们的示例项目如下,
github: thzt/react-tour/example-project

复制代码
example-project/
├── README.md
├── package.json
├── public
|  └── index.html
├── src
|  ├── App.js
|  └── index.js
└── yarn.lock

其中,index.js 的内容如下,

复制代码
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

App.js 的内容如下,

复制代码
const App = () => {
  debugger;
  return 'hello world'
};

export default App;

当前 React 项目只加载了一个 <App /> 组件,这个组件只返回了一段纯文本 'hello world'

React 初窥门径(一):环境准备 介绍的一致,我们可以启动项目,

复制代码
$ yarn
$ yarn start

# http://127.0.0.1:3000

3. 调试 Web 项目

参考 React 初窥门径(三):用 VSCode 调试

我们将 github: thzt/react-tour 中的文件,拷贝到 React 源码根目录,

  • package.json :直接覆盖,其中新增了 debug-build 这个 npm scripts
  • .vscode/ :拷贝到 React 源码目录,其中包含了两个 debug 配置,我们要用 Debug React 这个配置
  • .tour/ :VSCode CodeTour 插件的数据

整体操作流程如下:

(1)示例项目操作过程

(2)React 源码操作过程

  • 下载(克隆) React 源码,并切换到 v17.0.2
  • 拷贝 github: thzt/react-tour 中的 package.json .vscode/ .tour/ 到 React 源码目录
  • 选择 Debug React 选项进行调试

我们发现 VSCode 的断点停在了 example-project/src/App.js 文件中。

复制代码
const App = () => {
  debugger;              // <- 断点到了这里
  return 'hello world'
};

export default App;

4. 调用栈

我们先来跟踪一下,从 ReactDOM.render 到 App 组件 debugger 位置的调用栈,

CodeTour.tour/)中,也记录了这个过程,

4.1 组件加载过程:函数组件(call stack)

复制代码
render
legacyRenderSubtreeIntoContainer
unbatchedUpdates
fn
updateContainer
scheduleUpdateOnFiber
performSyncWorkOnRoot
renderRootSync
workLoopSync
performUnitOfWork
beginWork$1
beginWork
mountIndeterminateComponent
renderWithHooks

以上调用栈只展示了函数的链式调用关系,如果用缩进表示调用链路的话,它应该是这样的,

复制代码
render
  legacyRenderSubtreeIntoContainer
    unbatchedUpdates
      fn
        updateContainer
          scheduleUpdateOnFiber
            performSyncWorkOnRoot
              renderRootSync
                workLoopSync
                  performUnitOfWork
                    beginWork$1
                      beginWork
                        mountIndeterminateComponent
                          renderWithHooks

它表示 render 调用了 legacyRenderSubtreeIntoContainer
legacyRenderSubtreeIntoContainer 又调用了 unbatchedUpdates
unbatchedUpdates 又调用了 fn 等等,直到最后调用了 renderWithHooks

最后 renderWithHooks 调用了函数组件 App,来到断点那里。

5. 全流程

只看调用栈的话,React 组件的加载过程还不完整,我们知道某个函数之前别调用之前,是否还调用了其他函数,

以下我们整理了从 renderApp 调用的全流程(函数前面的数字,表示缩进层次)。

4.1.1 组件加载过程:函数组件(全流程)

(下图包含代码折叠,而且只截了一部分,完整版 请查看上面的链接)

6. render 和 commit 阶段

全流程包含了特别多的细碎逻辑,我们首先想弄明白的是,

  • 组件是何时被调用的,组件返回之后发生了什么(render 阶段)
  • 组件是如何展示在页面上的(commit 阶段)

这两个阶段,就是 performSyncWorkOnRoot 做的事情了,在大图中它处于这个位置,

可以看到:

  • render 阶段(renderRootSync :根据用户创建的 React 组件,创建 Fiber Tree(先从上到下 performUnitOfWork ,再从下到上 completeWork
  • commit 阶段(commitRoot:把 Fiber Tree 实际写入到 DOM 中

一图胜千言,(函数前面的数字,表示缩进层次)

复制代码
[6] performSyncWorkOnRoot
  [7] renderRootSync
    [8] markRenderStarted                                         <- render 阶段开始
    [8] workLoopSync
      [9] performUnitOfWork ---- [HostRoot {tag: 3}]              <- 从 根元素 开始向下构建 Fiber Tree
        [10] beginWork$1
          [11] beginWork
            [12] updateHostRoot                                   <- 加载 根元素 HostRoot
              [13] reconcileChildren
                [14] reconcileChildFibers                         <- 创建 child 子元素
                  [15] reconcileSingleElement
                    [16] createFiberFromElement
                      [17] createFiberFromTypeAndProps
                        [18] createFiber
      [9] performUnitOfWork ---- [IndeterminateComponent {tag: 2}] (<App />)
        [10] beginWork$1
          [11] beginWork
            [12] mountIndeterminateComponent                      <- 加载 函数组件 App
              [13] renderWithHooks
                [14] Component
              [13] reconcileChildren
                [14] mountChildFibers=reconcileChildFibers        <- 创建 child 子元素
                  [15] reconcileSingleTextNode
                    [16] deleteRemainingChildren
                    [16] createFiberFromText
                      [17] createFiber
      [9] performUnitOfWork ---- [HostText {tag: 6}] ('hello world')
        [10] beginWork$1
          [11] beginWork
            [12] updateHostText                                   <- 加载 纯文本 'hello world'
        [10] completeUnitOfWork                                   <- 开始倒着从 子节点 向上到 根节点 进行梳理
          [11] completeWork ---- [HostText {tag: 6}] ('hello world')
            [12] createTextInstance
              [13] createTextNode
                [14] createTextNode [HTMLElement] ('hello world') <- 实际创建 HTML
          [11] completeWork ---- [IndeterminateComponent {tag: 2}] (<App />)
          [11] completeWork ---- [HostRoot {tag: 3}]
    [8] markRenderStopped                                         <- render 阶段结束
  [7] commitRoot                                                  <- commit 阶段开始

下文我们再仔细介绍 commit 阶段。


参考

github: facebook/react v17.0.2
VSCode: CodeTour
github: thzt/react-tour
github: thzt/react-tour/example-project
React 初窥门径(一):环境准备
React 初窥门径(三):用 VSCode 调试
4.1 组件加载过程:函数组件(call stack)
4.1.1 组件加载过程:函数组件(全流程)
最后编辑于:2024-10-27 15:38:40
© 著作权归作者所有,转载或内容合作请联系作者

喜欢的朋友记得点赞、收藏、关注哦!!!

相关推荐
一只爱撸猫的程序猿3 小时前
构建一个简单的智能文档问答系统实例
数据库·spring boot·aigc
crud4 小时前
Spring Boot 3 整合 Swagger:打造现代化 API 文档系统(附完整代码 + 高级配置 + 最佳实践)
java·spring boot·swagger
a_157153249864 小时前
SpringCloud学习笔记-4
笔记·学习·spring cloud
鳄鱼杆5 小时前
服务器 | Centos 9 系统中,如何部署SpringBoot后端项目?
服务器·spring boot·centos
千|寻5 小时前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
保持学习ing5 小时前
Spring注解开发
java·深度学习·spring·框架
techzhi5 小时前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
酷爱码5 小时前
Spring Boot 整合 Apache Flink 的详细过程
spring boot·flink·apache
异常君6 小时前
Spring 中的 FactoryBean 与 BeanFactory:核心概念深度解析
java·spring·面试
cacyiol_Z6 小时前
在SpringBoot中使用AWS SDK实现邮箱验证码服务
java·spring boot·spring