第 3 部分
3.0 第 3 部分 (点击查看大图)
挂载
componentMount
方法是我们整个系列中极其重要的一个部分。如图,我们关注 ReactCompositeComponent.mountComponent
(1) 方法。
如果你还记得,我曾提到过 组件树的入口组件 是 TopLevelWrapper
组件 (React 底层内部类)。我们准备挂载它。由于它实际上是一个空的包装器,调试起来非常枯燥并且对实际的流程而言没有任何影响,所以我们跳过这个组件从他的孩子组件开始分析。
把组件挂载到组件树上的过程就是先挂载父亲组件,然后他的孩子组件,然后他的孩子的孩子组件,依次类推。可以肯定,当 TopLevelWrapper
挂载后,他的孩子组件 (用来管理 ExampleApplication
的组件 ReactCompositeComponent
) 也会在同一阶段注入。
现在我们回到步骤 (1) 观察这个方法的内部实现,有一些重要行为会发生,接下来让我们深入研究这些重要行为。
给实例赋值 updater
从 transaction.getUpdateQueue()
方法返回的 updater
见图中(2), 实际上就是 ReactUpdateQueue
模块。 为什么要在这里赋值一个 updater
呢?因为我们正在研究的类 ReactCompositeComponent
是一个全平台的共用的类,但是 updater
却依赖于平台环境有不同的实现,所以我们在这里根据不同的平台动态的将它赋值给实例。
然而,我们现在并不马上需要这个 updater
,但是你要记住它是非常重要的,因为它很快就会应用于非常知名的组件内更新方法 setState
。
事实上在这个过程中,不仅仅 updater
被赋值给实例,组件实例(你的自定义组件)也获得了继承的 props
, context
, 和 refs
。
观察以下的代码:
// \src\renderers\shared\stack\reconciler\ReactCompositeComponent.js#255
// 这些应该在构造方法里赋值,但是为了
// 使类的抽象更简单,我们在它之后赋值。
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
因此,你才可以通过一个实例从你的代码中获得 props
,比如 this.props
。
创建 ExampleApplication 实例
通过调用步骤 (3) 的方法 _constructComponent
然后经过几个构造方法的作用后,最终创建了 new ExampleApplication()
。这就是我们代码中构造方法第一次被执行的时机,当然也是我们的代码第一次实际接触到 React 的生态系统,很棒。
执行首次挂载
接着我们研究步骤 (4),第一个即将发生的行为是 componentWillMount
(当然仅当它被定义时) 的调用。这是我们遇到的第一个生命周期钩子函数。当然,在下面一点你会看到 componentDidMount
函数, 只不过这时由于它不能马上执行,而是被注入了一个事务队列中,在很后面执行。他会在挂载系列操作执行完毕后执行。当然你也可能在 componentWillMount
内部调用 setState
,在这种情况下 state
会被重新计算但此时不会调用 render
。(这是合理的,因为这时候组件还没有被挂载)
官方文档的解释也证明这一点:
componentWillMount()
在挂载执行之前执行,他会在render()
之前被调用,因此在这个过程中设置组件状态不会触发重绘。
观察以下的代码,进一步验证:
// \src\renderers\shared\stack\reconciler\ReactCompositeComponent.js#476
if (inst.componentWillMount) {
//..
inst.componentWillMount();
// 当挂载时, 在 `componentWillMount` 中调用的 `setState` 会执行并改变状态
// `this._pendingStateQueue` 不会触发重渲染
if (this._pendingStateQueue) {
inst.state = this._processPendingState(inst.props, inst.context);
}
}
确实如此,但是当 state 被重新计算完成后,会调用我们在组件中申明的 render 方法。再一次接触 “我们的” 代码。
接下来下一步就会创建一个 React 的组件的实例。然后呢?我们已经看见过步骤 (5) this._instantiateReactComponent
的调用了,对吗?是的。在那个时候它为我们的 ExampleApplication
组件实例化了 ReactCompositeComponent
,现在我们准备基于它的 render
方法获得的元素作为它的孩子创建 VDOM (虚拟 DOM) 实例。在我们的例子中,render
方法返回了一个div
,所以准确的 VDOM 元素是一个ReactDOMElement
。当该实例被创建后,我们会再次调用 ReactReconciler.mountComponent
,但是这次我们传入刚刚新创建的 ReactDOMComponent
实例作为internalInstance
。
然后继续调用此类中的 mountComponent
方法,这样递归往下…
好,第 3 部分我们讲完了
我们来回顾一下我们学到的。我们再看一下这种模式,然后去掉冗余的部分:
3.1 第 3 部分简化版 (点击查看大图)
让我们适度在调整一下:
3.2 第 3 部分简化和重构 (点击查看大图)
很好,实际上,下面的示意图就是我们所讲的。因此,我们可以理解第 3 部分的本质,并将其用于最终的 mount
方案:
3.3 第 3 部分本质 (点击查看大图)
完成!