上一章节《拆解SetState[一][一源看世界][之React]》找到了updater
的真面目为ReactUpdateQueue
,接下来就聊聊enqueueSetState
和enqueueCallback
吧
enqueueSetState:
enqueueSetState: function(publicInstance, partialState) {
...
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState'
);
if (!internalInstance) {
return;
}
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
enqueueCallback
enqueueCallback: function(publicInstance, callback, callerName) {
ReactUpdateQueue.validateCallback(callback, callerName);
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
...
if (!internalInstance) {
return null;
}
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
...
enqueueUpdate(internalInstance);
},
可以看出两个方法都引用了enqueueUpdate
方法。逻辑如下:
- 通过
getInternalInstanceReadyForUpdate
方法取得internalInstance
,这个实例是ReactCompositeComponentWrapper
实例(源码在instantiateReactComponent.js) - 对
internalInstance
进行修改
enqueueSetState
是将state
的更改放进_pendingStateQueue
队列;enqueueCallback
是将callback
回调方法放进_pendingCallbacks
队列 - 最后调用
enqueueUpdate
方法刷新所做的更改
我们去看一下enqueueUpdate
是怎么完成任务的吧
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
这里又有点令人费解!为什么这里的ReactUpdates
是直接引用而不是注入呢?那是因为ReactUpdates
提供了公用的方法,而它的依赖才是注入的。继续看看ReactUpdates
是怎么工作的。
function enqueueUpdate(component) {
ensureInjected();
...
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
从ensureInjected
的实现逻辑可得知ReactUpdates
的依赖是ReactUpdates.ReactReconcileTransaction
和batchingStrategy
batchingStrategy
是一种React如何批量更新的策略。当前只有一种策略,叫ReactDefaultBatchingStrategy
。
ReactReconcileTransaction
是跟环境相关的负责更新后的一些状态处理工作 - 比如DOM,它负责修复更新后会导致文本选择状态的丢失问题,在reconciliation期间禁止事件和将生命周期方法加入队列,具体可以看源码中它的Wrappers
var TRANSACTION_WRAPPERS = [
SELECTION_RESTORATION,
EVENT_SUPPRESSION,
ON_DOM_READY_QUEUEING,
];
上面代码的基本逻辑是:batchingStrategy
有一个属性告诉你当前是否处于事务之中,如果不是,enqueueUpdate
将它自己放入事务去执行;反之,将component(ReactCompositeComponentWrapper实例)放入dirtyComponents
数组中。
那接下来又会发生什么事呢?感觉水很深有木有。。。
为了弄清楚,我们得知道batchingStrategy
是在哪里注入,注入了哪个对象,又经过千山万水,在ReactDOM
中找到线索(ReactNative也有类似的注入)
ReactDefaultInjection.inject();
而在ReactDefaultInjection
源码中,可以清楚地看到注入的对象了,即ReactDefaultBatchingStrategy
ReactInjection.Updates.injectReconcileTransaction(
ReactReconcileTransaction
);
ReactInjection.Updates.injectBatchingStrategy(
ReactDefaultBatchingStrategy
);
研究一下ReactDefaultBatchingStrategy.batchedUpdates
方法,发现这是个事务方法,使用ReactDefaultBatchingStrategyTransaction
这个事务,对于事务,我们就只需要看Wrappers了,Wrappers有两个:
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
- RESET_BATCHED_UPDATES - 仅仅在事务结束时清理一下标识
- FLUSH_BATCHED_UPDATES - 在事务结束时执行
flushBatchedUpdates
方法,这个方法就是state更新的核心代码了。
那我们以热烈的掌声欢迎state更新中最重要最核心的主角上场:flushBatchedUpdates
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);
}
}
...
};
这里又有一种事务ReactUpdatesFlushTransaction
,它主要负责“捕获”任何在执行flushBatchedUpdates
后的pending中的更新。另外可以看出,这个事务是通过getPooled ()
(事务实例对象是提前准备好而不是实时创建 - 这样的好处是可以避免不必要的GC)取得的。
还有一个概念asap updates
,在接下来会好好讲讲。但现在,最需要的是休息,休息,好烧脑,敬请期待下一章节,不见不散。
最后,期待吐槽,期待指教!!!
--EOF--