上一章节《拆解setState[二][一源看世界][之React]》讲到了更新过程最核心的方法flushBatchedUpdates
,那我们接着聊
从flushBatchedUpdates
方法的源码中可以看出,它在ReactUpdatesFlushTransaction
这个事务中执行了runBatchedUpdates
方法,源码如下:
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
...
// Since reconciling a component higher in the owner hierarchy usually (not
// always -- see shouldComponentUpdate()) will reconcile children, reconcile
// them before their children by sorting the array.
dirtyComponents.sort(mountOrderComparator);
// Any updates enqueued while reconciling must be performed after this entire
// batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
// C, B could update twice in a single batch if C's render enqueues an update
// to B (since B would have already updated, we should skip it, and the only
// way we can know to do so is by checking the batch counter).
updateBatchNumber++;
for (var i = 0; i < len; i++) {
// If a component is unmounted before pending changes apply, it will still
// be here, but we assume that it has cleared its _pendingCallbacks and
// that performUpdateIfNecessary is a noop.
var component = dirtyComponents[i];
// If performUpdateIfNecessary happens to enqueue any new updates, we
// shouldn't execute the callbacks until the next render happens, so
// stash the callbacks first
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.props ===
component._renderedComponent._currentElement
) {
namedComponent = component._renderedComponent;
}
markerName = 'React update: ' + namedComponent.getName();
console.time(markerName);
}
ReactReconciler.performUpdateIfNecessary(
component,
transaction.reconcileTransaction,
updateBatchNumber
);
if (markerName) {
console.timeEnd(markerName);
}
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(
callbacks[j],
component.getPublicInstance()
);
}
}
}
}
这个方法遍历所有的dirty components,通过mount order进行排序(因为更新是从父级到子级),将所有setState的callback方法加入事务的队列,运行ReactReconciler
的performUpdateIfNecessary
方法。所以我们得去看看ReactReconciler
。
ReactReconciler & performUpdateIfNeeded - 最后的步骤了
直接看源码实现吧
performUpdateIfNecessary: function(
internalInstance,
transaction,
updateBatchNumber
) {
...
internalInstance.performUpdateIfNecessary(transaction);
...
},
啊,原来是调用了internalInstance的方法,在上一章节中我们说过internalInstance
是ReactCompositeComponentWrapper
实例,它的prototype继承了ReactCompositeComponent.Mixin
,所以我们很容易就在ReactCompositeComponent
找到了performUpdateIfNecessary
这个方法,看下实现吧
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;
}
},
这个方法分为两部分:
-
ReactReconciler.receiveComponent
- 在element级别去比较components。所以element实例比较后,如果它们不一样或者context改变了,就会触发internal instance的receiveComponent
-
this.updateComponent
将被调用,当有pending state的情况
你可能在想有必要检查pending state或者force updates吗?state必须处于pending状态那是因为你调用了setState,对吗?不是滴,updateComponent
是递归的所有你可以有更新的组件,但pending state是空的。同时对_pendingElement
的检查是用于处理children被更新的场景。
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext
) {
var inst = this._instance;
...
var willReceive = false;
var nextContext;
var nextProps;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
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) {
...
inst.componentWillReceiveProps(nextProps, nextContext);
...
}
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
...
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
...
}
...
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;
}
},
又是一个大方法!我们一步一步来解析它吧:
- 首先,先对context进行检查。如果改变了,context会被存进
nextContext
变量中。这里就不进行展开了。 -
updateComponent
检查props更新或者只是state更新。如果props更新,则触发componentWillReceiveProps
生命周期方法的执行 - 接下来是处理当前最新的state。
_processPendingState
方法就是用来搞定这事的 - 最后是判断component是否应该更新虚拟DOM,如果是,则通过
_performComponentUpdate
进行更新。如果不是,则仅仅是更新变量的值
瞧一瞧_processPendingState
吧
_processPendingState: function(props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
assign(
nextState,
typeof partial === 'function' ?
partial.call(inst, nextState, props, context) :
partial
);
}
return nextState;
},
可以看出设置和替换state共享同个队列_pendingStateQueue
,有一个属性_pendingReplaceState
用于判断是否替换。如果是,pending state将合并replaced state;如果不是则合并当前的state。
从源码中也可以看出setState
的第一个参数可以是个对象,也可以是一个函数,通过这个函数的入参可以拿到实例,当前最新的state, props和context,返回的是一个对象
ReactUpdates.asap
这是ReactUpdates
的一个重要特性,在asap
方法中实现
function asap(callback, context) {
...
asapCallbackQueue.enqueue(callback, context);
asapEnqueued = true;
}
它用在ReactUpdates
的flushBatchedUpdates
方法上,如:
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
这个主要用在input elements上。一般情况调用callback的策略如下:所有的更新(包括嵌套的更新)完成后,Callbacks才被调用。Asap使callback可以在当前的更新完成后立即调用 - 所以如果有嵌套的更新,必须等待asap callbacks完成后才能继续
Wow,设置state真是一个好长好长的流程啊,有没昏昏欲睡的感觉,来个总结吧
- 调用
setState
,它把pending state change和callbacks都丢给ReactUpdateQueue
处理 -
ReactUpdateQueue
更新component的internal instance,把所有更新存放到internal instance的队列变量上,然后交给ReactUpdates
-
ReactUpdates
利用batchingStrategy
来保证所有state更新在一个事务中执行和刷新 -
flushBatchedUpdates
负责同步地原子性地进行更新 -
ReactUpdatesFlushTransaction
保证了嵌套的更新被正确地处理 -
runBatchedUpdates
的职责是保证更新的顺序,即从父级到子级的顺序进行更新,然后调用ReactReconciler
来更新components -
performUpdateIfNecessary
的功能是判断是prop还是state更新,然后调用updateComponent
来处理更新 -
updateComponent
区分更新的类型,检查shouldComponentUpdate
的逻辑以判断是否要阻止虚拟DOM的更新。同时触发了一系列的生命周期方法(shouldComponentUpdate
,componentWillReceiveProps
,componentWillUpdate
,componentDidUpdate
) -
_processPendingState
用于处理pending state并返回当前最新的state对象值。它可以区分是部分设置还是整个替换,并且处理了第一个参数的不同类型入参逻辑(object vs function) - 最后介绍了asap callbacks,用于解决输入类型组件更新state的问题 - 它可以让callback在组件更新后立即执行,而不用等到所有嵌套组件完成更新才执行
最后,期待吐槽,期待指教!!!
--EOF--