setState过程分析
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
复制代码
大家可以看到我们平时调用的setState方法其实就是调用继承Component中的setState方法,从源码可以看到setState最终是调用this.updater.enqueusSetState方法,那么this.updater在哪里定义的呢,其实是在ReactCopositeComponent文件中组件的mountComponent中定义的,this.updater = updateQueue
-> var updateQueue = transaction.getUpdateQueue()
,那这个transacion事物又是哪里定义的,实际就是ReactReconcileTransaction中定义的,关于事物transaction我有一节专门讲解的,大家可以看看
getUpdateQueue: function () {
return ReactUpdateQueue;
}
复制代码
可以看到最终是掉用ReactUpdateQueue,那我们从ReactUpdateQueue文件看看enqueueSetState方法的具体实现
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
复制代码
可以看到我们传入的partialState最终是放在实例中**—pendingStateQueue队列中的,然后又调用ReactUpdates文件中的enqueueUpdate**方法,那我们看看具体实现:
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
复制代码
从上边代码可以看出逻辑是这样的:如果没有批量更新任务那就去更新,如果有就会把当前的实例放在dirtyComponents数组中,那我们再看看react的更新策略,在ReactDefaultBatchingStrategy文件中,看一下batchedUpdates源码的实现
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
}
复制代码
从上边可以看到,最终是以事物的形式去更行组件,那我们看一下更新策略中事物的源码:
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
复制代码
这边最终更新组件实际就是调用了ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),那我们看看具体实现
var flushBatchedUpdates = function () {
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);
}
}
};
复制代码
从上边可以看到实际就是调用了runBatchedUpdates,那我们看看这个方法的实现
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
updateBatchNumber++;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
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);
}
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
复制代码
上边代码的大概逻辑就是,遍历dirtyComponents去调用ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber)方法去更新组件,那我们接下来看一下ReactReconciler文件这个方法的实现
performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
if (process.env.NODE_ENV !== 'production') {
if (internalInstance._debugID !== 0) {
ReactInstrumentation.debugTool.onBeforeUpdateComponent(internalInstance._debugID, internalInstance._currentElement);
}
}
internalInstance.performUpdateIfNecessary(transaction);
if (process.env.NODE_ENV !== 'production') {
if (internalInstance._debugID !== 0) {
ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
}
}
}
复制代码
看到这个我们终于找到了调用组件实例performUpdateIfNecessary更新组件的方法
为什么setState是异步的呢
核心源码
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
_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;
}
复制代码
问题一,下面的结果为什么不是4而是2呢?
// 原来num = 1
this.setState({num: 2});
this.setState({num: 4})
复制代码
寻找原因:
-
state的数据是放在组件实例 _pendingStateQueue 上的,当每次调用setState时实际往这个队列里push
-
从上边源码可以看出,实际最终是调用Object.assign进行了合并操作
问题二:为什么回调函数中取到的是修改后的2,而下面取到却是未修改之前的值1呢
// 原来num = 1
this.setState({ num : 2 })
this.setState((nextState, props, context) => {
console.log(this.state.num) // 2
});
console.log(this.state.num) // 1
复制代码
原因:从源码可以看出,setState是异步的,它首先是放到队列里,最后统一去处理队列,所以下面取不到修改后的值,至于为什么在函数中能取到,这从上边源码很容易就能看出原因,
但是setState中第一个参数是函数在那些场景中能用到了,我暂时还没想到,大家有用到吗?