React源码解析——事务
setState可能是React最常用的API了,我们通过调用setState可以触发组件的更新,但调用后不能保证state会被同步更新。那么什么时候会同步更新,什么时候又是异步更新呢?这就和事务有关了。
setState的内部流程
先看源码对setState的定义:
function ReactComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
};
没有传入updater时,默认值为ReactNoopUpdateQueue,该值的enqueueCallback主要起到一个在非生产环境中警告的作用,没有实际意义,关键在于找出updater是在什么时候传入的。以ReactCompositeComponent类型组件为例,传入时刻是在构造时的_constructComponentWithoutOwner方法中:
return new Component(publicProps, publicContext, updatequeue);
可以看到updater就是updateQueue对象,接下来看其更新state的enqueueSetState方法:
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue
|| (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
该方法通过getInternalInstanceReadyForUpdate方法获取当前组件实例的React内部表达,其包含了组件实例内部才存在的一些属性,我们只需要关注两个关键属性:待更新队列(_pengdingStateQueue)和更新回调队列(_pengdingCallbacks)。我们将当前待更新的State推入到queue中,再执行enenqueUpdate方法。
enqueueUpdate: function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
该方法首先执行了ensureInjected方法,实际上就是一个保证ReactReconcileTransaction和batchingStraregy存在的验证方法,这两个属性下文会用到,如果不存在就会给出警告。接下来会根据batchingStrategy的值来决定是直接将组件实例放入dirtyComponents还是执行batchingStrategy.bathedUpdates。先看batchingStrategy的定义:
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
transaction.perform(callback, null, a, b, c, d, e);
}
},
};
这是一种批量更新策略,其属性isBatchingUpdates表示是否处于批量更新中,默认值为false,将执行transaction的perform方法,为true时直接执行callback。接下来的就是setState更新的关键——事务(Transaction)了。
Transaction
首先看一下源码中的ASCII绘制的流程图:
/**
* <pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
*/
该图形象的解释了事务的原理,其perform方法会给真正的方法包装wrapper,每个 wrapper都有initiallize和close方法,当执行真实的方法时会在之前和之后执行initialize和close。那么我们来看下transaction的定义:
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
Object.assign(
ReactDefaultBatchingStrategyTransaction.prototype,
Transaction.Mixin,
{
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
}
);
var transaction = new ReactDefaultBatchingStrategyTransaction();
可以看到RESET_BATCHED_UPDATED的wrapper会在事务结束时重置isBatchingUpdates为false。再梳理一下enqueueUpdate的流程:
- 最初执行时,isBatchingUpdates为false,将其置为true后执行perform方法。
- 开始事务的perform再次执行enqueueUpdate将组件推入dirtyComponents数组。
- 执行事务的close将isBatchingUpdates重置为false。
现在我们明白了RESET_BATCHED_UPDATES的作用,那么处在同一事务中的FLUSH_BATCHED_UPDATES又有什么用处呢?先看其源码:
var flushBatchedUpdates = function() {
while (dirtyComponents.length) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
//......
}
};
可以看到其遍历了所有的dirtyComponents,并在其中以事务ReactUpdatesFlushTransaction的方式执行runBatchedUpdates。
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
//.....
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
//.......
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(
callbacks[j],
component.getPublicInstance()
);
}
}
}
}
该方法首先将dirtyComponents以挂载顺序进行排序,为了保证父组件在其子组件之前更新,然后获取setState完成后的所有回调函数,再执行 ReactReconciler.performUpdateIfNecessary方法,该方法即用来触发组件的更新流程了,更新的详细流程在以后的文章中分析,现在需要知道的关键一点在于shouldComponentUpdate之前会执行_processPendingState来处理我们setState时创建的队列——pendingStateQueue。其源码如下:
_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 = Object.assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
Object.assign(
nextState,
typeof partial === 'function' ?
partial.call(inst, nextState, props, context) :
partial
);
}
return nextState;
}
可以看到该函数会对同一生命周期内的state变化进行合并后再统一处理,如果setState传入对象会通过assign合并,如果传入函数会将最新的state和props传入并执行后返回的对象再通过assgin合并。
总结
综上,setState的更新是同步或异步的关键在于目前所处ReactDefaultBatching事务的状态。当该事务开启时会将state变化的组件存入数组,在事务结束时调用wrapper的close去关闭事务标志位并批量处理state和组件的更新。当该事务关闭时会立即开启该事务并同步的完成该事务,就是同步更新state的表现了。
-- EOF --