资讯专栏INFORMATION COLUMN

React 源码深度解读(三):首次 DOM 元素渲染 - Part 3

U2FsdGVkX1x / 1018人阅读

摘要:在学习源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。到此为止,首次渲染就完成啦总结从启动到元素渲染到页面,并不像看起来这么简单,中间经历了复杂的层级调用。

前言

React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习 React 源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。本文会大量用到原文中的例子,想体会原汁原味的感觉,推荐阅读原文。

本系列文章基于 React 15.4.2 ,以下是本系列其它文章的传送门:
React 源码深度解读(一):首次 DOM 元素渲染 - Part 1
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
React 源码深度解读(三):首次 DOM 元素渲染 - Part 3
React 源码深度解读(四):首次自定义组件渲染 - Part 1
React 源码深度解读(五):首次自定义组件渲染 - Part 2
React 源码深度解读(六):依赖注入
React 源码深度解读(七):事务 - Part 1
React 源码深度解读(八):事务 - Part 2
React 源码深度解读(九):单个元素更新
React 源码深度解读(十):Diff 算法详解

正文

上一篇讲解了平台无关的代码,这篇继续来讲针对与 HTML DOM 操作的代码。

|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer()                 |
  |-ReactMount._renderNewRootComponent()                   |
    |-instantiateReactComponent()                          |
    |~batchedMountComponentIntoNode()                  upper half
      |~mountComponentIntoNode()                       (平台无关)
        |-ReactReconciler.mountComponent()                 |
          |-ReactCompositeComponent.mountComponent()       |
          |-ReactCompositeComponent.performInitialMount()  |
            |-instantiateReactComponent()                 _|_
            |-ReactDOMComponent.mountComponent()       lower half
        |-_mountImageIntoNode()                      (HTML DOM 相关)
                                                          _|_

先来看看 ReactDOMComponent.mountComponent 做了什么:

// 文件位置:src/renderers/dom/shared/ReactDomComponent.js

tComponent: function (
    transaction,
    hostParent,
    hostContainerInfo,
    context
) {
    ...

    var mountImage;
    
    var ownerDocument = hostContainerInfo._ownerDocument;
    var el;
    
    // document.createElement 创建 h1 元素
    el = ownerDocument.createElement(this._currentElement
                .type, props.is);

    ...
  
    // 创建 component 与 DOM 的双向链接
    // ReactDOMComponent[ins]._hostNode 指向 DOM
    // DOM 的 __reactInternalInstance 指向 component
    ReactDOMComponentTree.precacheNode(this, el);
    
    // 设置属性
    this._updateDOMProperties(null, props, transaction);
    
    var lazyTree = DOMLazyTree(el);
    
    // 循环创建子元素
    this._createInitialChildren(transaction, props, context,
        lazyTree);
        
    mountImage = lazyTree;

    ...

    return mountImage;
},

到此为止,实例间的关系是这样的:

DOM 元素创建完成后,剩下的就是将其挂载到 container 上面去了。这里调用的是 ReactMount 的 _mountImageIntoNode:

|=ReactMount.render(nextElement, container, callback)     ___
|=ReactMount._renderSubtreeIntoContainer()                 |
  |-ReactMount._renderNewRootComponent()                   |
    |-instantiateReactComponent()                          |
    |~batchedMountComponentIntoNode()                  upper half
      |~mountComponentIntoNode(                        (平台无关)
          wrapperInstance, // scr: -> not of interest now  |
          container,   // scr: --> document.getElementById(‘root’)
          transaction, // scr: --> not of interest         |
          shouldReuseMarkup, // scr: -------> null         |
          context,           // scr: -------> not of interest
        )                                                  |
        |-ReactReconciler.mountComponent()                 |
          |-ReactCompositeComponent.mountComponent()       |
          |-ReactCompositeComponent.performInitialMount()  |
            |-instantiateReactComponent()                 _|_
            |-ReactDOMComponent.mountComponent()           |
       /* we are here */                               lower half
        |-_mountImageIntoNode()                      (HTML DOM 相关)
            markup,    // scr: --> DOMLazyTree[ins]        |
            container, // scr: --> document.getElementById(‘root’)
            wrapperInstance, // scr:----> same             |
            shouldReuseMarkup, // scr:--> same             |
            transaction, // scr: -------> same             |
          )    

具体实现:

_mountImageIntoNode: function (
        markup,        // DOMLazyTree[ins] 
        container,     // document.getElementById(‘root’)
        instance,
        shouldReuseMarkup,
        transaction
    ) {
        ...
        
        while (container.lastChild) {
            container.removeChild(container.lastChild);
        }
        
        DOMLazyTree.insertTreeBefore(container, markup, null);

        ...
    }

DOMLazyTree.insertTreeBefore 最终会调用 parentNode.insertBefore,将元素挂载到 container 上。到此为止,首次渲染就完成啦!

总结

从 React 启动到元素渲染到页面,并不像看起来这么简单,中间经历了复杂的层级调用。原文的这张图总结得非常好:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98522.html

相关文章

  • React 源码深度解读(四):首次自定义组件渲染 - Part 1

    摘要:本篇开始介绍自定义组件是如何渲染的。组件将自定义组件命名为,结构如下经过编译后,生成如下代码构建顶层包装组件跟普通元素渲染一样,第一步先会执行创建为的。调用顺序已在代码中注释。先看图,这部分内容将在下回分解 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非...

    Warren 评论0 收藏0
  • React 源码深度解读(一):首次DOM元素渲染 - Part 1

    摘要:调用栈是这样的这里生成的我们将其命名为,它将作为参数传入到。整个的调用栈是这样的组件间的层级结构是这样的到此为止,顶层对象已经构造完毕,下一步就是调用来自的方法,进行页面的渲染了。通过表达的结构最终会转化为一个纯对象,用于下一步的渲染。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言...

    daydream 评论0 收藏0
  • React 源码深度解读(二):首次 DOM 元素渲染 - Part 2

    摘要:本文将要讲解的调用栈是下面这个样子的平台无关相关如果看源码,我们会留意到很多相关的代码,我们暂时先将其忽略,会在后续的文章中进行讲解。现在我们来看一下各实例间的关系目前为止的调用栈平台无关相关下一篇讲解三总结本文讲解了转化为的过程。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 R...

    wean 评论0 收藏0
  • React 源码深度解读(五):首次自定义组件渲染 - Part 2

    摘要:的和真正有效的都各只有一行代码的调用栈如下这中间的函数调用逻辑很清晰,最终会走到这里这里的逻辑很简单,如果不是数组,则调用回调函数如果是数组,则继续调用自身,相当于深度优先遍历。这里的回调函数就是中的这里直接调用,创建。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读...

    william 评论0 收藏0
  • React 源码深度解读(八):事务 - Part 2

    摘要:前言是一个十分庞大的库,由于要同时考虑和,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常...

    airborne007 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<