美文网首页Front End
[FE] React 初窥门径(四):React 组件的加载过程

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

作者: 何幻 | 来源:发表于2021-10-27 20:04 被阅读0次

    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 组件加载过程:函数组件(全流程)

    相关文章

      网友评论

        本文标题:[FE] React 初窥门径(四):React 组件的加载过程

        本文链接:https://www.haomeiwen.com/subject/dsvpaltx.html