[FE] React 初窺門(mén)徑(四):React 組件的加載過(guò)程(render 階段)

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ò)程

(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ù),
以下我們整理了從 renderApp 調(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ù)組件(全流程)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容