[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)示例项目操作过程
- 下载 github: thzt/react-tour/example-project 示例项目
- 给示例项目安装依赖:
yarn
- 替换
example-project
中node_modules
中的依赖react
和react-dom
参考 React 初窥门径(一):环境准备
可使用工具 github: thzt/react-tour/tool/link.bash - 启动示例项目:
yarn start
,http://127.0.0.1:3000
(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 组件的加载过程还不完整,我们知道某个函数之前别调用之前,是否还调用了其他函数,
以下我们整理了从 render
到 App
调用的全流程(函数前面的数字,表示缩进层次)。
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 组件加载过程:函数组件(全流程)
喜欢的朋友记得点赞、收藏、关注哦!!!