继续上文。。。flushBatchedUpdates 的源码
var flushBatchedUpdates = function () {
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// array and perform any updates enqueued by mount-ready handlers (i.e.,
// componentDidUpdate) but we need to check here too in order to catch
// updates enqueued by setState callbacks and asap calls.
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
在此又出现了一个新的事物ReactUpdatesFlushTransaction,它主要用来捕获在运行flushBatchedUpdate后将要运行的updates。这个过程比较复杂,因为componentDidUpdate或则setState后的回调方法需要进入下一个更新队列。另外这个事物是getpooled来的,而不是实时创建的,这样做的好处是避免不必要的垃圾收集。另外这个地方也涉及到asp update的内容,后续将介绍到。
在上述源码中,需要在ReactUpdatesFlushTransaction事物的输入参数中调用了runBatchedUpdates,我们来看一看这个方法的逻辑功能:
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
//父组件通常比子组件有更高的reconcile等级,所以需要对dirtyComponents进行排序
dirtyComponents.sort(mountOrderComparator);
// reconciling时任何更新队列在整个批量处理后必须执行。然而,假设dirtyComponents是[A,B],B和C是A的child如果C的渲染器为B进行入队列更新,那么在一次批量处理中B需要更新两次。由于B已经更新了,我们将会忽略它,再次更新它的方式只能通过检查bact的数量
updateBatchNumber++;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
//如果,在任何新的更新队列中发生了performUpdateIfNecessary,那么只能在下一次发生渲染时执行回调所以,先将回调存储下来。(后面来看一看什么是performUpdateIfNecessary)
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
// Duck type TopLevelWrapper. This is probably always true.
if (component._currentElement.type.isReactTopLevelWrapper) {
namedComponent = component._renderedComponent;
}
markerName = 'React update: ' + namedComponent.getName();
console.time(markerName);
}
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
if (markerName) {
console.timeEnd(markerName);
}
// 将回调函数放入transaction的回调队列中
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
这个方法遍历所有的dirtyComponent,并根据渲染的顺序对它们进行排序,比如从父节点到子节点的开始更新。运行来自ReactReconciler对象下的performUpdateIfNecessary方法,并将回调函数放入transaction队列中。(修正:runBatchedUpdates的逻辑功能是先排序保证先渲染父组件再渲染子组件,再执行必要的更新,最后将回调函数写入callbackQueue。
现在到最后一步了ReactReconciler
2-3 ReactReconciler&performUpdateIfNecessary
因为,internalInstance是ReactCompositeComponentWrapper实例,它的prototype继承了ReactCompositeComponent.Mixin,所以可以在ReactCompositeComponent下找到performUpdateIfNecessary这个方法,来看一看它的源码:
performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
// 组件的批量处理个数应该和当前或者接下来的批量处理个数相同
if (internalInstance._updateBatchNumber !== updateBatchNumber) {
return;
}
…
internalInstance.performUpdateIfNecessary(transaction);
…
}
};
ReactCompositeComponent文件下找到performUpdateIfNecessary 方法:
/**
* If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`
* is set, update the component.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(
this,
this._pendingElement,
transaction,
this._context);
}
else if (this._pendingStateQueue !== null || this._pendingForceUpdate)
{
this.updateComponent(transaction,
this._currentElement,
this._currentElement,
this._context, this._context);
} else {
this._updateBatchNumber = null;
}
}
这个方法被分成了两个部分:
(1) ReactReconciler.receiveComponent是用来在element级别中比较Component,所以element实例被比较后,如果component和element实例不同或者内容发生了变化,那么将会调用内部实例的receiveComponent方法。如果组件相同时,会调用updateComponent方法,这个方法包含了检测逻辑功能。
(2) updateComponent 如果存在pending state,将会调用这个方法
你可能会思考,为什么需要检测pending状态或者强迫更新,由于调用了setState,所以state处于pending状态?其实不是这样的。真正的原因是updateComponent是递归地更新的组件,但pending state是空的。而且,对_pendingElement的检查是用于处理children被更新的场景。下面来看一看updateComponent的具体实现方法:
updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
var inst = this._instance;
!(inst != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Attempted to update component `%s` that has already been unmounted (or failed to mount).', this.getName() || 'ReactCompositeComponent') : _prodInvariant('136', this.getName() || 'ReactCompositeComponent') : void 0;
var willReceive = false;
var nextContext;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;
// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
willReceive = true;
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
if (process.env.NODE_ENV !== 'production') {
measureLifeCyclePerf(function () {
return inst.componentWillReceiveProps(nextProps, nextContext);
}, this._debugID, 'componentWillReceiveProps');
} else {
inst.componentWillReceiveProps(nextProps, nextContext);
}
}
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) {
if (process.env.NODE_ENV !== 'production') {
shouldUpdate = measureLifeCyclePerf(function () {
return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
}, this._debugID, 'shouldComponentUpdate');
} else {
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
}
} else {
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}
}
}
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_ENV !== 'production' ? warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : void 0;
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
},
接下来我们一步步分析这个逻辑方法的步骤:
(1) 首先,判断内容是否发生了变化,如果内容发生了变化将context,将nextUnmaskedContext经过this._processContext()方法处理后存储到nextContext
(2) 检测props或者state是否发生改变,如果props发生改变,那么state没有更新,props有更新,则调用componentWillReceiveProps声明周期方法,更新props
(3) _processPendingState来处理state更新,如果需要更新,则从_pendingStateQueue获取新的replace的标志位以及最新的state。
(4) 判断component是否需要更新虚拟DOM,如果需要更新则调用_performComponentUpdate,否则只对props、state、context进行更新。
最后来介绍之前说到的ReactUpdates文件中flushBatchedUpdates方法中的asp。
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
/**
* Enqueue a callback to be run at the end of the current batching cycle. Throws
* if no updates are currently being performed.
*/
function asap(callback, context) {
invariant(batchingStrategy.isBatchingUpdates, "ReactUpdates.asap: Can't enqueue an asap callback in a context where" + 'updates are not being batched.');
asapCallbackQueue.enqueue(callback, context);
asapEnqueued = true;
}
Asap的功能是使当前更新内容结束后调用asapCallbackQueue中的回调函数,如果没更新,则立即调用。
修改:
原理就说到这里了,乱的不得了,下面我们将梳理具体的流程。
网友评论