本文基于React v15.6.2版本介绍,原因请参见新手如何学习React源码
在上一篇文章的最后,我们走到了mountComponentIntoNode,它通过调用ReactReconciler.mountComponent来获取Markup,这个也是React执行挂载的核心入口,源码位于src/renderers/shared/stack/reconciler/ReactReconciler.js:
Copy mountComponent : function (
internalInstance ,
transaction ,
hostParent ,
hostContainerInfo ,
context ,
parentDebugID , // 0 in production and for roots
) {
var markup = internalInstance . mountComponent (
transaction ,
hostParent ,
hostContainerInfo ,
context ,
parentDebugID ,
) ;
if (
internalInstance . _currentElement &&
internalInstance . _currentElement . ref != null
) {
transaction . getReactMountReady () . enqueue ( attachRefs , internalInstance ) ;
}
return markup ;
}, 可以看到这个方法的核心只是调用instance的mountComponent方法,这个instance是我们一开始进来的wrapperInstance,也就是React自己包裹的那一层组件,它是一个CompositeComponent,注意这里的参数顺序换了,实际上其他参数没有变,hostParent在我们初次挂载的时候为null。
一般来说,真正去产生markup的是真实的DOM节点,而不是抽象的React组件,顺序一般是ReactCompositeComponent -> ReactDOMComponent -> ReactDOMTextComponent/ReactEmptyComponent,所以一开始去看一下ReactCompositeComponent是明智的选择。
源码位于src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:
对于React组件的mountComponent过程,主要做了3件事:
执行performInitialMount,获得Markup
其中第二步处理是最复杂的,我们先来看看1和3:
首先我们先拿到React组件本身:
这里的_currentElement,其实就是我们JSX所调用的createElement的返回值,这一点之前已经详细介绍过,而对于React组件来说,它的整个class都是挂载type这个字段下的,所以这里通过type就可以拿到React组件的类。
接着有一个shouldConstruct的判断:
我们知道,React组件有两种模式:类组件和函数组件。
对于类组件的写法之前已经分析过,它会在prototype上挂一个isReactComponent属性,所以这里的判断是用来区分类组件和函数组件的。
紧接着开始实例化,实例化过程最终会走到这个函数:
可以看到这里函数组件就直接执行函数了,所以这里拿到的实例有两种情况:
所以有下面这段代码来修正一下函数组件的instance:
这里对于函数组件,会实例化一个StatelessComponent的实例出来:
这个实例只挂载了一个render方法,用来执行函数组件的渲染。这个render方法会在后续更新流程中被统一调用。
处理componentDidMount回调
这里其实之前看过了,队列会在transaction周期的末尾被触发,而上个函数本身是个递归的过程,根据队列的特性,这里叶子节点的componentDidMount就会被先触发。
在这个performInitialMount核心实现中,主要做了3件事:
实际上,如果说在上一步处理的是当前组件的信息,那么这一步主要就处理的是它的渲染组件的信息,也就是它的子组件,首先执行钩子,并初始化子组件的控制类,接着执行子组件的mountComponent方法。
注意一下这里的细节,如果是函数组件,实际上之前相当于已经执行过render了,所以这里看触发render只针对类组件。
而挂载子组件则可以触发一个递归的过程,最终的markup还是通过挂载子组件取到。
我们知道一个React组件并不实际会被浏览器加载,只有到达DOM节点时才会被渲染到浏览器中,所以markup只有在这里递归子组件走到了叶子节点,才会有真正的实质内容出现。
上面主要分析了React组件内部是如何实现挂载的,实际上对于一个ReactCompositeComponent来说,最终是不会被挂载到浏览器上的,它主要在reconciler目录下实现,表示这里的逻辑实际上和平台无关,而是React自身的底层逻辑,我们把重要的步骤画一个图:
实际上,通过实例化、执行render、执行生命周期、递归子组件挂载的过程,就是整个React组件挂载的全貌了,而真正处理挂载的细节逻辑是在叶子节点(DOM处理)上,下篇文章我们将仔细讨论下叶子节点的挂载过程。