先看一下第二部分都是什么
提前说一下: 我们知道React的diff策略中有一条,不要改变组件的最外部元素,比如 原本是p,被改成了div。React的diff策略会认为这两个是完全不一样的组件,即使,两者只是改了一个元素而已。 而这也是 Reactdiff算的复杂度从 O(n^3)将到 O(N)的重要原因。
接下来我们继续来看,根组件的挂啊在这个流程的第二部分,也就是当prevComponent存在的时候,也就是在调用方法
ReactDOM.render(<App/>,document.getElementById('app')
的时候,id为app的div内已经有内容了,也就是已经被挂载了组件。 这个时候就是要替换的时候了。
我们知道 instancesByReactRootID 内存的是 根实例与根组件id的一一对应的对象。 一般单页面应用,单个 container的话就只有一项。 prevComponent 存在说明,不是初次加载,而是切换根组件
var prevComponent = instancesByReactRootID[getReactRootID(container)];
if (prevComponent) {
'
prevComponent 表示已经存在的根组件,而组件的 _currentElement属性存储的是根组件的 Element表示形式。
这里的 prevComponent 则是 id为app的根元素包装成的ReactCompositeComponent类的实例。
一个prevComponent 如下图一所示:
'
var prevWrappedElement = prevComponent._currentElement;
'
这里才是真正的根组件 element信息,比如App组件
'
var prevElement = prevWrappedElement.props;
'
shouldUpdateReactComponent方法来判断一下是否要更新
'
if (shouldUpdateReactComponent(prevElement, nextElement)) {
'
如果这两执行了,那就说明,已经存在的根组件,与将要替换他的组件类型一致,只是小改动,这时候就需要用到更新方法。
_renderedComponent 属性指向一个组件的挂载实例。
挂载组件的 _renderedComponent属性指向的是 一个挂载实例,这个挂载实例是 prevComponent包含的根组件的挂载实例。
publicInst如下图二所示:
'
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback = callback && function () {
callback.call(publicInst);
};
'
调用 更新方法 _updateRootComponent 更新组件。
_updateRootComponent方法在下边。
'
ReactMount._updateRootComponent(prevComponent, nextWrappedElement, container, updatedCallback);
return publicInst;
} else {
'
到了这里,就将现在存在的根组件直接卸载掉。
'
// 直接卸载 container 内的组件
ReactMount.unmountComponentAtNode(container);
}
}
图一
图二:
shouldUpdateReactComponent
prevElement 和 nextElement 一样的话,就返回true。 比如 prevElement和 nextElement 都为null或者是都为false的时候返回true prevElement和 nextElement 都为 string 类型 或者是都为 number 类型的时候返回true prevElement和 nextElement 都为Object类型,切两者的key相等。则返回true、
猜测:返回true的话就说明 要替换 prevElement 的 nextElement 与 prevElement是同一个组件只是内容不一样,可以选择对其进行更新 操作。 而如果 返回了false 说明,要替换的东西nextElement和 prevElement不是一个东西,不如完全的卸载 prevElement, 而后直接将 nextElement挂载上去来的性能高。
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;
if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}
var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
return nextType === 'object' &&
prevElement.type === nextElement.type
&& prevElement.key === nextElement.key;
}
return false;
}
getPublicInstance
这个方法就是要返回 挂载实例的 _instance 属性。 而this._instance则是将 当前挂载实例的 Element.type的实例化。 当然是只有自定义的组件才有这个属性。
getPublicInstance: function () {
var inst = this._instance;
if (inst instanceof StatelessComponent) {
return null;
}
return inst;
},
ReactMount._updateRootComponent 更新渲染的核心方法
这里用到了scrollMonitor方法。 该方法是一个钩子函数,其作用是 在更新的时候 报保持 容器的外观滚动位置不变。
_updateRootComponent: function (prevComponent, nextElement, container, callback) {
ReactMount.scrollMonitor(container, function () {
'
核心的方法
'
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
if (callback) {
ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
}
});
return prevComponent;
},
ReactUpdateQueue.enqueueElementInternal 方法
这个方法如下,核心的作用是 在 组件实例 internalInstance的 属性 _pendingElement 赋值为 要更新的组件。
最后调用了 当前模块下的 enqueueUpdate 方法
enqueueElementInternal: function (internalInstance, newElement) {
internalInstance._pendingElement = newElement;
enqueueUpdate(internalInstance);
}
看一下 enqueueUpdate 方法
这个方法很简单 只是调用了 ReactUpdates.enqueueUpdate 这里记一下 internalInstance参数指的是旧的挂载组件,而且旧的挂载组件有个属性上赋值了新的 Element
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
ReactUpdates.enqueueUpdate 方法
这里要说一下,batchingStrategy.isBatchingUpdates 的值为true。
首先。batchingStrategy 是 ReactDefaultBatchingStrategy类 isBatchingUpdates 是类的属性。默认是false。只有在类方法 batchedUpdates 被调用的时候 属性 isBatchingUpdates 的值变为true, 这里的核心是 batchedUpdates 是什么时候调用的。 我们这里上下文中是没有调用 batchedUpdates 方法的。
注意: 在 事件分发机制中我们提到过 dispatchEvent 方法,该方法会调用 ReactUpdates.batchedUpdates,而React组件的更新则是由事件触发的,而不是随意的就进行更新,所以这里的 ReactUpdates.batchedUpdates 方法 调用了 修改了 isBatchingUpdates 为true。
所以该方法就只是 将 internalInstance 放入 dirtyComponents 队列中。
这里 internalInstance 是一个挂在实例 dirtyComponents数组中放置的也就是 挂载实例。
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
到此,就完了,但是,完结的有点奇怪,明明到此为止,根本看不到什么更新的动作。 这里要注意的一点是 事务 事务会在方法执行前后塞入方法来执行。 所以,这里的问题是 究竟用了哪个事务?
这里还是要回说到 事件分发的 dispatchEvent 方法。 这个方法不单单 调用 ReactDefaultBatchingStrategy 类的 batchedUpdates 方法 将 ReactDefaultBatchingStrategy 类的 isBatchingUpdates 属性修改为true,而是同时是调用了一个 Transaction ---- ReactDefaultBatchingStrategyTransaction
ReactDefaultBatchingStrategyTransaction 事务
transaction.perform(callback, null, a, b, c, d, e);
关于事务执行完毕之后要做的事情,我们当然是要看看 Wrapperl了
TRANSACTION_WRAPPERS
主要是以下两个 Wrapper。 第一个只是在 批处理事务执行完毕的时候 将属性 isBatchingUpdates 置为 false。
第二个则是在 事务处理完毕的时候 执行 ReactUpdates.flushBatchedUpdates 方法。 所有,找到,接下来要做的事情在 ReactUpdates.flushBatchedUpdates方法内
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
我们就可以接着 dirtyComponents 往下走
ReactUpdates.flushBatchedUpdates 方法
var flushBatchedUpdates = function () {
'
首先是遍历 dirtyComponents 队列
'
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
'
调一个 ReactUpdatesFlushTransaction 实例出来
'
var transaction = ReactUpdatesFlushTransaction.getPooled();
'
使用取出来的事务来执行 runBatchedUpdates 方法
'
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
先来看看 ReactUpdatesFlushTransaction 事务
核心还是让我们看看 事务的Wrapper
就是在 事务结束的时候,执行callbackQueue 的方法。
var UPDATE_QUEUEING = {
initialize: function () {
this.callbackQueue.reset();
},
close: function () {
this.callbackQueue.notifyAll();
}
};
ReactUpdatesFlushTransaction 事务的作用很简单,核心还是要看一下事务要包裹的方法 runBatchedUpdates
ReactUpdate的 runBatchedUpdates 方法
function runBatchedUpdates(transaction) {
'
先拿到 dirtyComponents 队列的长度
这里 transaction 属性 dirtyComponentsLength 是 在事务的 Wrapper 中的 initialize 方法中初始化的。
'
var len = transaction.dirtyComponentsLength;
'
dirtyComponents 进行排序,排序的规则是 dirtyComponents 队列里每一项的 _mountOrder 大小来进行排序的。(从小到大)
'
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
'
取出来每一项来遍历操作
component 是一个挂载实例。
_pendingCallbacks 在下边有详细说明
'
var component = dirtyComponents[i];
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
'
调用 performUpdateIfNecessary 方法
performUpdateIfNecessary是 component 本身所有的方法。
'
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
'
如果 callbacks 存在就将这些callbacks 入队,待得
'
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
_pendingCallbacks
_pendingCallbacks 是挂载实例的一个熟悉,主要用来存储回调函数,一个setState的回调函数
在调用setState的时候会向组件挂载实例的属性 _pendingCallbacks添加一setState方法的第二个参数(回调函数)。 这一点在 setState中有说明
看一下 performUpdateIfNecessary 方法
ReactCompositeComponent 模块下的 performUpdateIfNecessary
这里要说一下,这一系列指示要挂载根组件,所以,dirtyComponents中只会存两种挂载实例: 一 最外层根元素 id为app的div的包装挂载实例
二 根组件 App 的挂载实例。
这两个挂载实例都是 ReactCompositeComponent 的子类。所以可以调用 ReactCompositeComponent 模块下的 performUpdateIfNecessary 方法。
简单的来说 performUpdateIfNecessary 方法的作用是 :
flush 一个组件中的任何 dirty 变化。
React中对这个方法有解释:
If any of _pendingElement, _pendingStateQueue, or _pendingForceUpdate
is set, update the component.
也就是三个属性中的任何一个被设定有值就更新组件。 关于这三个属性参考这篇文章
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
'
用一个新 element 来更新组件
'
ReactReconciler.receiveComponent(this, this._pendingElement || this._currentElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
'
updateComponent方法是对已经安装的组件进行更新,componentWillReceiveProps 和 shouldComponentUpdate 两个方法会被调用,
'
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
},
_pendingElement
为更新队列设定的属性,是自定义组件的挂载实例的一个属性。初始值为null。 一般情况下其为 null。
这里在 ReactMount._updateRootComponent 方法中调用下边的代码,为 _pendingElement 赋值
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
ReactReconciler.receiveComponent
这个方法的主要作用是 用一个新的 element来更新一个组件。
receiveComponent: function (internalInstance, nextElement, transaction, context) {
'
如果 prevElement (上一个 挂载的 Element) 与 nextElement(接下来要挂载的Element)一样。
直接返回,因为没啥要改的。
'
var prevElement = internalInstance._currentElement;
if (nextElement === prevElement && context === internalInstance._context) {
return;
}
'
计算一下是否要更新 refs
'
var refsChanged = ReactRef.shouldUpdateRefs(prevElement, nextElement);
'
移除旧的ref指向,将ref指向新的element的ref属性
'
if (refsChanged) {
ReactRef.detachRefs(internalInstance, prevElement);
}
'
而后调用 组件挂载实例的 receiveComponent方法
'
internalInstance.receiveComponent(nextElement, transaction, context);
if (refsChanged && internalInstance._currentElement && internalInstance._currentElement.ref != null) {
transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
}
},
receiveComponent
receiveComponent方法很简单,就是 调用了挂载实例的 更新方法 updateComponent。
receiveComponent: function (nextElement, transaction, nextContext) {
var prevElement = this._currentElement;
var prevContext = this._context;
this._pendingElement = null;
this.updateComponent(transaction, prevElement, nextElement, prevContext, nextContext);
},
关于 updateComponent 方法,请参考文章 综合组件的更新方法
到此为止,根组件被更新/替换了。 当然我们知道 这些是因为 shouldUpdateReactComponent 方法执行为true的时候。
这个时候是因为要用来替换的根组件与原组件是一个东西,可能只是内容不太一样,所以就采用了更新的方法。这样性能更好
而如果:shouldUpdateReactComponent方法返回false。也就是要用来替换的根组件和原来组件不一样。
直接将当前根组件卸载了,而后重新的将要替换的组件当做新组件进行渲染。
ReactMount.unmountComponentAtNode(container);
有些啰嗦了。
简言
这一部分针对的是 根组件被替换的时候。 也据说ReactDOM.render方法被重复调用,根元素(id为app的div元素)内的根组件App被替换的时候。所做的事情。
如果是替换一个新的根组件,那就是直接卸载老组件而后将新组件渲染。
而如果是更新,就需要进行更新,一些细致化的操作。