React源码学习入门(七)详解ReactMount入口

本文基于React v15.6.2版本介绍,原因请参见新手如何学习React源码

源码分析

ReactMount的源码位于src/renderers/dom/client/ReactMount.js

ReactMount中,我们常用的API是renderunmountComponentAtNode,而render则是整个应用的启动入口:

render的首次初始化核心方法实现在_renderNewRootComponent中:

这个方法核心做了几件事情:

  1. 实例化nextElement,创建对应的辅助类

  2. 调用batchedUpdates

  3. 设置_instancesByReactRootID,这个被devtools使用,不重要

前两步其实都非常核心,我们展开讲解一下。

首先,实例化的这个nextElement是什么?实际上在调用链的上一层函数_renderSubtreeIntoContainer可以找到:

实际上是React自己调用createElement创建的一个Element,而它的props的child是nextElement,这个element就是我们调用render的时候传入的根组件了。

那么TopLevelWrapper是什么呢?在ReactMount中是这么定义的:

实际上就是一个ReactComponent,而它挂了一个成员rootID,就是一层容器,React给我们传入的Element又包裹了一层Wrapper组件,主要目的还是为了统一创建一个ReactCompositeComponent的控制类,上篇文章讲了React有四种控制类,而我们自己传入的根组件并不能确认是属于哪种类型,于是React为了更好地处理多次调用render的更新逻辑,就统一包了一个WrapperComponnent

所以这里在_renderNewRootComponent时,调用instantiateReactComponent创建出来的实际上是Wrapper的instance,我们观察一下被传入batchedUpdate的几个参数:

  • componentInstance,是Wrapper的控制类,是一个ReactCompositeComponent

  • container,是我们的DOM容器

  • shouldReuseMarkup,与二次render有关,先忽略

  • context,与parent的context有关,这里属于非根节点render的场景,先忽略

在Mount过程中调用batchedUpdate,其实是为了在rending的生命周期中,例如componentWillMount或者componentDidMount,去调用setState更新能够做到批量更新一次,这个细节我们在后面的文章里面再细说。

接着我们看一下batchedMountComponentIntoNode

这个方法核心是开启了ReactReconcileTransaction,这个transaction将会伴随mount的整个周期,这里采用之前讲过的对象池来做复用,不再赘述。

接下来看一下这个transaction是什么,源码位于src/renderers/dom/client/ReactReconcileTransaction.js

可以看到这个transaction上挂载了几个属性和方法,需要关注的是reactMountReady是一个队列,这个在挂载过程中会存放componentDidMount的回调,挂载完成后依次触发,所以说componentDidMount整体其实是异步的。

接下来再看一下wrapper的部分:

前两个和保存事件状态有关,而第三个Wrapper则是处理队列的通知,保证执行完Mount之后回调能够正常触发。

最后我们看一下mountComponentIntoNode的实现,这个方法也是整个Mount流程的最后一步:

这里核心做了两件事:

  1. 调用ReactReconciler.mountComponent拿到markup,这个是个递归的过程,也是stack核心,限于篇幅我们在后续文章中详解,可以认为最终拿到的markup是一个已经处理好的DOM节点(开启createElement的新版本),或是要插入的HTML片段(老版本)。

  2. 调用ReactMount._mountImageIntoNode去挂载到真实的DOM容器下。

这里额外注意的一点是新增加了一个参数containerInfo,我们看一下ReactDOMContainerInfo,源码位于src/renderers/dom/shared/ReactDOMContainerInfo.js

生成的containerInfo主要挂载了topLevelWrapper的实例,container本身的节点信息,和一个idCounter,这些信息在后续更新过程中十分有用,可以稍微记住这里,后续更新再回过头来看。

最后再稍微看下_mountImageIntoNode,实际上在首次挂载时它的执行逻辑非常简单:

可以看到主要是调用DOMLazyTree.insertTreeBefore去插入节点,DOMLazyTree存在的意义是为了让IE系列有更好的性能,React团队调研发现直接插入大量节点在IE/Edge下不如小批量插入节点来得快(相差10倍以上的性能),因此这个文件专门用来磨平差异,我们这里可以简单理解为它就是原生的insertBefore方法。

小结一下

上述过程中讲了非常多ReactMount的细节,实际上很多都是为了Update去做准备,我们抛开Update不谈,只看Mount,实际上非常简单,整个Mount它就做了三件事情:

  1. 创建了一个根节点(在内部是统一包裹了Wrapper组件)实例。

  2. 调用ReactReconciler.mountComponent获取Markup,这个是个递归的过程。

  3. 调用mountImageIntoNode将Markup挂载到容器的DOM节点上。

Last updated