1. 回顧
前幾篇文章中,我們采用了 VSCode 插件 CodeTour 來(lái)記錄代碼的執(zhí)行過(guò)程,
并把相關(guān)的數(shù)據(jù) .tour/ 放到了 github: thzt/react-tour 中。
截止到本文為之,我們總共記錄了這些 code-tour,
.tour/
├── 2. 構(gòu)建過(guò)程.tour
├── 3.1 react 的加載過(guò)程.tour
├── 3.2 react-dom 的加載過(guò)程.tour
├── 4.1 組件加載過(guò)程:函數(shù)組件(call stack).tour
├── 4.1.1 組件加載過(guò)程:函數(shù)組件(全流程).tour
└── 4.2 組件加載過(guò)程:類(lèi)組件(call stack).tour
本文重點(diǎn)介紹 4.1.1 組件加載過(guò)程:函數(shù)組件(全流程) 相關(guān)的內(nèi)容。
2. 極簡(jiǎn)的示例項(xiàng)目
現(xiàn)在我們開(kāi)始介紹 React 函數(shù)組件的加載全流程,我們的示例項(xiàng)目如下,
github: thzt/react-tour/example-project
example-project/
├── README.md
├── package.json
├── public
| └── index.html
├── src
| ├── App.js
| └── index.js
└── yarn.lock
其中,index.js 的內(nèi)容如下,
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
App.js 的內(nèi)容如下,
const App = () => {
debugger;
return 'hello world'
};
export default App;
當(dāng)前 React 項(xiàng)目只加載了一個(gè) <App /> 組件,這個(gè)組件只返回了一段純文本 'hello world'。
與 React 初窺門(mén)徑(一):環(huán)境準(zhǔn)備 介紹的一致,我們可以啟動(dòng)項(xiàng)目,
$ yarn
$ yarn start
# http://127.0.0.1:3000
3. 調(diào)試 Web 項(xiàng)目
參考 React 初窺門(mén)徑(三):用 VSCode 調(diào)試,
我們將 github: thzt/react-tour 中的文件,拷貝到 React 源碼根目錄,
-
package.json:直接覆蓋,其中新增了
debug-build這個(gè) npm scripts -
.vscode/:拷貝到 React 源碼目錄,其中包含了兩個(gè) debug 配置,我們要用
Debug React這個(gè)配置 - .tour/:VSCode CodeTour 插件的數(shù)據(jù)
整體操作流程如下:
(1)示例項(xiàng)目操作過(guò)程
- 下載 github: thzt/react-tour/example-project 示例項(xiàng)目
- 給示例項(xiàng)目安裝依賴(lài):
yarn - 替換
example-project中node_modules中的依賴(lài)react和react-dom
參考 React 初窺門(mén)徑(一):環(huán)境準(zhǔn)備
可使用工具 github: thzt/react-tour/tool/link.bash - 啟動(dòng)示例項(xiàng)目:
yarn start,http://127.0.0.1:3000
(2)React 源碼操作過(guò)程
- 下載(克?。?React 源碼,并切換到 v17.0.2
- 拷貝 github: thzt/react-tour 中的
package.json.vscode/.tour/到 React 源碼目錄 - 選擇
Debug React選項(xiàng)進(jìn)行調(diào)試

我們發(fā)現(xiàn) VSCode 的斷點(diǎn)停在了 example-project/src/App.js 文件中。
const App = () => {
debugger; // <- 斷點(diǎn)到了這里
return 'hello world'
};
export default App;
4. 調(diào)用棧
我們先來(lái)跟蹤一下,從 ReactDOM.render 到 App 組件 debugger 位置的調(diào)用棧,

在 CodeTour(.tour/)中,也記錄了這個(gè)過(guò)程,

4.1 組件加載過(guò)程:函數(shù)組件(call stack)
render
legacyRenderSubtreeIntoContainer
unbatchedUpdates
fn
updateContainer
scheduleUpdateOnFiber
performSyncWorkOnRoot
renderRootSync
workLoopSync
performUnitOfWork
beginWork$1
beginWork
mountIndeterminateComponent
renderWithHooks
以上調(diào)用棧只展示了函數(shù)的鏈?zhǔn)秸{(diào)用關(guān)系,如果用縮進(jìn)表示調(diào)用鏈路的話(huà),它應(yīng)該是這樣的,
render
legacyRenderSubtreeIntoContainer
unbatchedUpdates
fn
updateContainer
scheduleUpdateOnFiber
performSyncWorkOnRoot
renderRootSync
workLoopSync
performUnitOfWork
beginWork$1
beginWork
mountIndeterminateComponent
renderWithHooks
它表示 render 調(diào)用了 legacyRenderSubtreeIntoContainer,
legacyRenderSubtreeIntoContainer 又調(diào)用了 unbatchedUpdates,
unbatchedUpdates 又調(diào)用了 fn 等等,直到最后調(diào)用了 renderWithHooks。
最后 renderWithHooks 調(diào)用了函數(shù)組件 App,來(lái)到斷點(diǎn)那里。
5. 全流程
只看調(diào)用棧的話(huà),React 組件的加載過(guò)程還不完整,我們知道某個(gè)函數(shù)之前別調(diào)用之前,是否還調(diào)用了其他函數(shù),
以下我們整理了從 render 到 App 調(diào)用的全流程(函數(shù)前面的數(shù)字,表示縮進(jìn)層次)。
4.1.1 組件加載過(guò)程:函數(shù)組件(全流程)
(下圖包含代碼折疊,而且只截了一部分,完整版請(qǐng)查看上面的鏈接)

6. render 和 commit 階段
全流程包含了特別多的細(xì)碎邏輯,我們首先想弄明白的是,
- 組件是何時(shí)被調(diào)用的,組件返回之后發(fā)生了什么(render 階段)
- 組件是如何展示在頁(yè)面上的(commit 階段)
這兩個(gè)階段,就是 performSyncWorkOnRoot 做的事情了,在大圖中它處于這個(gè)位置,

可以看到:
-
render 階段(
renderRootSync):根據(jù)用戶(hù)創(chuàng)建的 React 組件,創(chuàng)建 Fiber Tree(先從上到下performUnitOfWork,再?gòu)南碌缴?completeWork) -
commit 階段(
commitRoot):把 Fiber Tree 實(shí)際寫(xiě)入到 DOM 中
一圖勝千言,(函數(shù)前面的數(shù)字,表示縮進(jìn)層次)
[6] performSyncWorkOnRoot
[7] renderRootSync
[8] markRenderStarted <- render 階段開(kāi)始
[8] workLoopSync
[9] performUnitOfWork ---- [HostRoot {tag: 3}] <- 從 根元素 開(kāi)始向下構(gòu)建 Fiber Tree
[10] beginWork$1
[11] beginWork
[12] updateHostRoot <- 加載 根元素 HostRoot
[13] reconcileChildren
[14] reconcileChildFibers <- 創(chuàng)建 child 子元素
[15] reconcileSingleElement
[16] createFiberFromElement
[17] createFiberFromTypeAndProps
[18] createFiber
[9] performUnitOfWork ---- [IndeterminateComponent {tag: 2}] (<App />)
[10] beginWork$1
[11] beginWork
[12] mountIndeterminateComponent <- 加載 函數(shù)組件 App
[13] renderWithHooks
[14] Component
[13] reconcileChildren
[14] mountChildFibers=reconcileChildFibers <- 創(chuàng)建 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 <- 開(kāi)始倒著從 子節(jié)點(diǎn) 向上到 根節(jié)點(diǎn) 進(jìn)行梳理
[11] completeWork ---- [HostText {tag: 6}] ('hello world')
[12] createTextInstance
[13] createTextNode
[14] createTextNode [HTMLElement] ('hello world') <- 實(shí)際創(chuàng)建 HTML
[11] completeWork ---- [IndeterminateComponent {tag: 2}] (<App />)
[11] completeWork ---- [HostRoot {tag: 3}]
[8] markRenderStopped <- render 階段結(jié)束
[7] commitRoot <- commit 階段開(kāi)始
下文我們?cè)僮屑?xì)介紹 commit 階段。
參考
github: facebook/react v17.0.2
VSCode: CodeTour
github: thzt/react-tour
github: thzt/react-tour/example-project
React 初窺門(mén)徑(一):環(huán)境準(zhǔn)備
React 初窺門(mén)徑(三):用 VSCode 調(diào)試
4.1 組件加載過(guò)程:函數(shù)組件(call stack)
4.1.1 組件加載過(guò)程:函數(shù)組件(全流程)