上一章節(jié)《拆解setState[二][一源看世界][之React]》講到了更新過(guò)程最核心的方法flushBatchedUpdates
,那我們接著聊
從flushBatchedUpdates
方法的源碼中可以看出耍属,它在ReactUpdatesFlushTransaction
這個(gè)事務(wù)中執(zhí)行了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()
);
}
}
}
}
這個(gè)方法遍歷所有的dirty components蚯撩,通過(guò)mount order進(jìn)行排序(因?yàn)楦率菑母讣?jí)到子級(jí))逗爹,將所有setState的callback方法加入事務(wù)的隊(duì)列绑蔫,運(yùn)行ReactReconciler
的performUpdateIfNecessary
方法蛋哭。所以我們得去看看ReactReconciler
县习。
ReactReconciler & performUpdateIfNeeded - 最后的步驟了
直接看源碼實(shí)現(xiàn)吧
performUpdateIfNecessary: function(
internalInstance,
transaction,
updateBatchNumber
) {
...
internalInstance.performUpdateIfNecessary(transaction);
...
},
啊,原來(lái)是調(diào)用了internalInstance的方法谆趾,在上一章節(jié)中我們說(shuō)過(guò)internalInstance
是ReactCompositeComponentWrapper
實(shí)例躁愿,它的prototype繼承了ReactCompositeComponent.Mixin
,所以我們很容易就在ReactCompositeComponent
找到了performUpdateIfNecessary
這個(gè)方法沪蓬,看下實(shí)現(xiàn)吧
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;
}
},
這個(gè)方法分為兩部分:
-
ReactReconciler.receiveComponent
- 在element級(jí)別去比較components彤钟。所以element實(shí)例比較后,如果它們不一樣或者context改變了跷叉,就會(huì)觸發(fā)internal instance的receiveComponent
-
this.updateComponent
將被調(diào)用逸雹,當(dāng)有pending state的情況
你可能在想有必要檢查pending state或者force updates嗎?state必須處于pending狀態(tài)那是因?yàn)槟阏{(diào)用了setState云挟,對(duì)嗎梆砸?不是滴,updateComponent
是遞歸的所有你可以有更新的組件园欣,但pending state是空的帖世。同時(shí)對(duì)_pendingElement
的檢查是用于處理children被更新的場(chǎng)景。
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;
}
},
又是一個(gè)大方法沸枯!我們一步一步來(lái)解析它吧:
- 首先日矫,先對(duì)context進(jìn)行檢查。如果改變了辉饱,context會(huì)被存進(jìn)
nextContext
變量中搬男。這里就不進(jìn)行展開了。 -
updateComponent
檢查props更新或者只是state更新彭沼。如果props更新缔逛,則觸發(fā)componentWillReceiveProps
生命周期方法的執(zhí)行 - 接下來(lái)是處理當(dāng)前最新的state。
_processPendingState
方法就是用來(lái)搞定這事的 - 最后是判斷component是否應(yīng)該更新虛擬DOM姓惑,如果是褐奴,則通過(guò)
_performComponentUpdate
進(jìn)行更新。如果不是于毙,則僅僅是更新變量的值
瞧一瞧_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;
},
可以看出設(shè)置和替換state共享同個(gè)隊(duì)列_pendingStateQueue
敦冬,有一個(gè)屬性_pendingReplaceState
用于判斷是否替換。如果是唯沮,pending state將合并replaced state脖旱;如果不是則合并當(dāng)前的state堪遂。
從源碼中也可以看出setState
的第一個(gè)參數(shù)可以是個(gè)對(duì)象,也可以是一個(gè)函數(shù)萌庆,通過(guò)這個(gè)函數(shù)的入?yún)⒖梢阅玫綄?shí)例溶褪,當(dāng)前最新的state, props和context,返回的是一個(gè)對(duì)象
ReactUpdates.asap
這是ReactUpdates
的一個(gè)重要特性践险,在asap
方法中實(shí)現(xiàn)
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);
}
這個(gè)主要用在input elements上。一般情況調(diào)用callback的策略如下:所有的更新(包括嵌套的更新)完成后巍虫,Callbacks才被調(diào)用彭则。Asap使callback可以在當(dāng)前的更新完成后立即調(diào)用 - 所以如果有嵌套的更新,必須等待asap callbacks完成后才能繼續(xù)
Wow占遥,設(shè)置state真是一個(gè)好長(zhǎng)好長(zhǎng)的流程啊俯抖,有沒(méi)昏昏欲睡的感覺(jué),來(lái)個(gè)總結(jié)吧
- 調(diào)用
setState
筷频,它把pending state change和callbacks都丟給ReactUpdateQueue
處理 -
ReactUpdateQueue
更新component的internal instance蚌成,把所有更新存放到internal instance的隊(duì)列變量上前痘,然后交給ReactUpdates
-
ReactUpdates
利用batchingStrategy
來(lái)保證所有state更新在一個(gè)事務(wù)中執(zhí)行和刷新 -
flushBatchedUpdates
負(fù)責(zé)同步地原子性地進(jìn)行更新 -
ReactUpdatesFlushTransaction
保證了嵌套的更新被正確地處理 -
runBatchedUpdates
的職責(zé)是保證更新的順序凛捏,即從父級(jí)到子級(jí)的順序進(jìn)行更新,然后調(diào)用ReactReconciler
來(lái)更新components -
performUpdateIfNecessary
的功能是判斷是prop還是state更新芹缔,然后調(diào)用updateComponent
來(lái)處理更新 -
updateComponent
區(qū)分更新的類型坯癣,檢查shouldComponentUpdate
的邏輯以判斷是否要阻止虛擬DOM的更新。同時(shí)觸發(fā)了一系列的生命周期方法(shouldComponentUpdate
最欠,componentWillReceiveProps
示罗,componentWillUpdate
,componentDidUpdate
) -
_processPendingState
用于處理pending state并返回當(dāng)前最新的state對(duì)象值芝硬。它可以區(qū)分是部分設(shè)置還是整個(gè)替換蚜点,并且處理了第一個(gè)參數(shù)的不同類型入?yún)⑦壿?object vs function) - 最后介紹了asap callbacks蛙酪,用于解決輸入類型組件更新state的問(wèn)題 - 它可以讓callback在組件更新后立即執(zhí)行贷币,而不用等到所有嵌套組件完成更新才執(zhí)行
最后元媚,期待吐槽贴届,期待指教@奚贰C胄菜枷!
--EOF--