本文使用的是react 15.6.1的代碼
在上篇文章組件的渲染中_renderNewRootComponent中最后調(diào)用了ReactUpdates.batchedUpdates函數(shù)什乙,上文中只是簡(jiǎn)單的分析了最后實(shí)際是在內(nèi)部調(diào)用了其中的第一個(gè)參數(shù)batchedMountComponentIntoNode,其實(shí)并沒有那么簡(jiǎn)單秕噪,今天我們就來看看batchedUpdates實(shí)際做了些什么內(nèi)容
ReactUpdates.batchedUpdates
//批量更新方法
function batchedUpdates(callback, a, b, c, d, e) {
ensureInjected(); //不用理會(huì),單純的打印方法
return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}
實(shí)際上是調(diào)用了batchingStrategy.batchedUpdates中的方法玉转,但是翻遍了整個(gè)文件钢属,發(fā)現(xiàn)batchingStrategy這個(gè)對(duì)象一直是null,那么batchingStrategy是怎么初始化的呢?在ReactUpdates中郊艘,發(fā)現(xiàn)定義了這樣一個(gè)對(duì)象ReactUpdatesInjection玻佩,其中代碼如下
var ReactUpdatesInjection = {
injectReconcileTransaction: function(ReconcileTransaction) {
ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
},
injectBatchingStrategy: function(_batchingStrategy) {
batchingStrategy = _batchingStrategy;
},
};
其中有一個(gè)方法injectBatchingStrategy
出嘹,那么是不是外部誰調(diào)用了該方法,使得最后batchingStrategy被注入進(jìn)來了咬崔,在ReactDOM源碼中税稼,我們發(fā)現(xiàn)有一行
ReactDefaultInjection.inject(); //注入事件烦秩,環(huán)境變量,各種參數(shù)值
查看ReactDefaultInjection.inject()方法后郎仆,又找到了這樣一行代碼
ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);
再次查看ReactInjection中的源代碼
var ReactInjection = {
Component: ReactComponentEnvironment.injection,
DOMProperty: DOMProperty.injection,
EmptyComponent: ReactEmptyComponent.injection,
EventPluginHub: EventPluginHub.injection,
EventPluginUtils: EventPluginUtils.injection,
EventEmitter: ReactBrowserEventEmitter.injection,
HostComponent: ReactHostComponent.injection,
Updates: ReactUpdates.injection,
};
也就是說只祠,在ReactDOM之中,會(huì)執(zhí)行到 ReactUpdates.injection.injectBatchingStrategy
方法扰肌,同時(shí)抛寝,將ReactDefaultBatchingStrategy
傳入其中,所以最后batchingStrategy = ReactDefaultBatchingStrategy
,那么回到前文batchingStrategy.batchedUpdates(callback, a, b, c, d, e)
曙旭,這里面的batchedUpdates
實(shí)際就是執(zhí)行的ReactDefaultBatchingStrategy中的方法
源碼地址[ReactDefaultBatchingStrategy]
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
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);//重點(diǎn)代碼
}
},
};
代碼很簡(jiǎn)單盗舰,第一次調(diào)用的時(shí)候ReactDefaultBatchingStrategy.isBatchingUpdates肯定是false,那么就會(huì)調(diào)用transaction.perform方法,并將相應(yīng)回調(diào)傳入其中,那么我們來看看ReactDefaultBatchingStrategy相關(guān)代碼
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();
}
// ReactDefaultBatchingStrategyTransaction.prototype添加Transaction對(duì)象的方法桂躏,如reinitializeTransaction钻趋,同時(shí)用新的函數(shù)覆蓋getTransactionWrappers
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
var transaction = new ReactDefaultBatchingStrategyTransaction();
發(fā)現(xiàn)transation是ReactDefaultBatchingStrategyTransaction的實(shí)例,但是perform方法是Transaction.js提供的剂习,我們看看Transaction方法
'use strict';
var invariant = require('invariant');
var OBSERVED_ERROR = {};
/**
* `Transaction` creates a black box that is able to wrap any method such that
* certain invariants are maintained before and after the method is invoked
* (Even if an exception is thrown while invoking the wrapped method). Whoever
* instantiates a transaction can provide enforcers of the invariants at
* creation time. The `Transaction` class itself will supply one additional
* automatic invariant for you - the invariant that any transaction instance
* should not be run while it is already being run. You would typically create a
* single instance of a `Transaction` for reuse multiple times, that potentially
* is used to wrap several different methods. Wrappers are extremely simple -
* they only require implementing two methods.
*
* <pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
*
* Use cases:
* - Preserving the input selection ranges before/after reconciliation.
* Restoring selection even in the event of an unexpected error.
* - Deactivating events while rearranging the DOM, preventing blurs/focuses,
* while guaranteeing that afterwards, the event system is reactivated.
* - Flushing a queue of collected DOM mutations to the main UI thread after a
* reconciliation takes place in a worker thread.
* - Invoking any collected `componentDidUpdate` callbacks after rendering new
* content.
* - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
* to preserve the `scrollTop` (an automatic scroll aware DOM).
* - (Future use case): Layout calculations before and after DOM updates.
*
* Transactional plugin API:
* - A module that has an `initialize` method that returns any precomputation.
* - and a `close` method that accepts the precomputation. `close` is invoked
* when the wrapped process is completed, or has failed.
*
* @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
* that implement `initialize` and `close`.
* @return {Transaction} Single transaction for reuse in thread.
*
* @class Transaction
*/
var TransactionImpl = {
reinitializeTransaction: function(): void {
//獲取wrappers
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
_isInTransaction: false,
/**
* @abstract
* @return {Array<TransactionWrapper>} Array of transaction wrappers.
* 由具體Transaction來提供蛮位,如ReactDefaultBatchingStrategyTransaction
*/
getTransactionWrappers: null,
isInTransaction: function(): boolean {
return !!this._isInTransaction;
},
perform: function<
A,
B,
C,
D,
E,
F,
G,
T: (a: A, b: B, c: C, d: D, e: E, f: F) => G,
>(method: T, scope: any, a: A, b: B, c: C, d: D, e: E, f: F): G {
var errorThrown;
var ret;
try {
//正在Transaction ing中
this._isInTransaction = true;
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
//如果initializeAll沒有拋出異常的話
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
//進(jìn)入close生命周期
this.closeAll(0);
}
} finally {
// Transaction結(jié)束
this._isInTransaction = false;
}
}
return ret;
},
initializeAll: function(startIndex: number): void {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = OBSERVED_ERROR;
//調(diào)用wrapper的initialize方法
this.wrapperInitData[i] = wrapper.initialize
? wrapper.initialize.call(this)
: null;
} finally {
//如果調(diào)用initialize有問題,則startIndex+1在調(diào)用
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
closeAll: function(startIndex: number): void {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
},
};
export type Transaction = typeof TransactionImpl;
module.exports = TransactionImpl;
代碼比較少鳞绕,從代碼注釋的簡(jiǎn)圖看失仁,在創(chuàng)建Transations的時(shí)候,會(huì)先注入wrapper,當(dāng)調(diào)用perform方法的時(shí)候猾昆,會(huì)依次調(diào)用wrapper的initialize方法陶因,隨后在調(diào)用perform中傳入的方法骡苞,最后在調(diào)用依次調(diào)用wrapper中的close方法垂蜗,簡(jiǎn)單看下來,有設(shè)計(jì)模式(代理)
的思路在里面,我們具體看perform的實(shí)現(xiàn)首先解幽,調(diào)用了initializeAll
方法贴见,然后循環(huán)拿到transactionWrappers依次調(diào)用initialize方法,隨后調(diào)用perform的第一參數(shù)躲株,最后又調(diào)用closeAll
方法片部,依次調(diào)用transactionWrappers中的close的方法,最后將_isInTransaction
置為false表示transation結(jié)束霜定。和圖中描述的一樣档悠,也就是說,不同的Transation會(huì)去實(shí)現(xiàn)不同的wrapper來代理具體的方法望浩,那么ReactDefaultBatchingStrategyTransaction
的wrapper是什么辖所?在ReactDefaultBatchingStrategy之中,實(shí)例化ReactDefaultBatchingStrategyTransaction
時(shí)會(huì)調(diào)用
this.reinitializeTransaction()方法磨德,該方法中調(diào)用了getTransactionWrappers
獲取具體的wrappers缘回,對(duì)應(yīng)的吆视,ReactDefaultBatchingStrategyTransaction
重寫了getTransactionWrappers
方法,因此酥宴,得到返回值
[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]
這是wrapper的實(shí)現(xiàn)
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
代碼比較簡(jiǎn)單啦吧,兩個(gè)wraper都只有close方法,根據(jù)代碼描述拙寡,會(huì)先執(zhí)行FLUSH_BATCHED_UPDATES的close方法也就是ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
var flushBatchedUpdates = function() {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
// 從緩存池中獲取ReactUpdatesFlushTransaction對(duì)象
var transaction = ReactUpdatesFlushTransaction.getPooled();
// 調(diào)用runBatchedUpdates
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
react運(yùn)行中當(dāng)存在臟組件的時(shí)候授滓,會(huì)用transaction調(diào)用runBatchedUpdates
方法,在執(zhí)行之前,react使用了對(duì)象池這樣的優(yōu)化手段來獲取/生成transaction對(duì)象倒庵,該部分暫時(shí)跳過褒墨,后面在細(xì)讀,我們?cè)趤砜纯磖unBatchedUpdates做了些什么
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
// 排序擎宝,保證 dirtyComponent 從父級(jí)到子級(jí)的 render 順序
dirtyComponents.sort(mountOrderComparator);
updateBatchNumber++;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
// 獲取該組件的回調(diào)數(shù)組
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);
}
// 更新該 dirtyComponent 重點(diǎn)代碼
ReactReconciler.performUpdateIfNecessary(
component,
transaction.reconcileTransaction,
updateBatchNumber,
);
if (markerName) {
console.timeEnd(markerName);
}
// 如果存在組件回調(diào)函數(shù)郁妈,將其放入回調(diào)隊(duì)列中
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(
callbacks[j],
component.getPublicInstance(),
);
}
}
}
}
代碼不多,首先是將臟components進(jìn)行排序绍申,變成從父組件到子組件的順序排列噩咪,隨后遍歷組件調(diào)用performUpdateIfNecessary更新數(shù)據(jù),同時(shí)將組件的回調(diào)壓入回調(diào)隊(duì)列中极阅,等待最后調(diào)用胃碾,這里我們注意到并不是更新一個(gè)組件的時(shí)候就調(diào)用其回調(diào),而是等到所有組件都更新完以后才去調(diào)用筋搏,看看核心代碼performUpdateIfNecessary方法仆百,內(nèi)部就一行核心代碼internalInstance.performUpdateIfNecessary(transaction)
即調(diào)用了ReactComponent對(duì)象的performUpdateIfNecessary方法,即ReactCompositeComponent.performUpdateIfNecessary方法
performUpdateIfNecessary: function(transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(
this,
this._pendingElement,
transaction,
this._context,
);
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
//注意奔脐,調(diào)用了this.updateComponent方法俄周,但是element以及content都是component自己的即_currentElement,_context
this.updateComponent(
transaction,
this._currentElement,
this._currentElement,
this._context,
this._context,
);
} else {
this._updateBatchNumber = null;
}
}
繞了一圈,最后還是調(diào)用組件自己的updateComponent方法髓迎,而updateComponent會(huì)去執(zhí)行React組件的生命周期峦朗,如componentWillReceiveProps,shouldComponentUpdate排龄,componentWillUpdate波势,render, componentDidUpdate。完成整套流程橄维。后面我們慢慢介紹React的生命周期尺铣。
回到上面代碼,剛才知道runBatchedUpdates是有事務(wù)去調(diào)用的争舞,react事務(wù)會(huì)先執(zhí)行wrapper中的init方法凛忿,在執(zhí)行close方法,我們?cè)趤砜纯碦eactUpdatesFlushTransaction的wrapper相關(guān)代碼做了什么兑障。
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
// 在批量更新侄非,如果有新的dirtyComponents被push蕉汪,那么,需要再一次批量更新逞怨,從新加入的dirtyComponents開始
if (this.dirtyComponentsLength !== dirtyComponents.length) {
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates();
} else {
dirtyComponents.length = 0;
}
},
};
var UPDATE_QUEUEING = {
initialize: function() {
// 重置回調(diào)隊(duì)列
this.callbackQueue.reset();
},
close: function() {
// 執(zhí)行回調(diào)方法
this.callbackQueue.notifyAll();
},
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
看到上面代碼者疤,不得不感嘆react設(shè)計(jì)的巧妙,這里巧妙的使用了transition的生命周期叠赦,在initialize時(shí)獲取當(dāng)前dirtyComponent的個(gè)數(shù)驹马,最后結(jié)束(close)的時(shí)候再次校驗(yàn),如果數(shù)量不對(duì)除秀,那么說明dirtyComponents有新增糯累,再一次進(jìn)行flushBatchedUpdates,因?yàn)楦虏僮魇桥康牟岵龋虼瞬粫?huì)出現(xiàn)改一個(gè)就執(zhí)行一次的情況泳姐,大大提高了性能,第二個(gè)wrapper主要是處理回調(diào)隊(duì)列里面的內(nèi)容暂吉,當(dāng)更新處理完成后胖秒,最后調(diào)用UPDATE_QUEUEING close方法,調(diào)用notifyAll
去執(zhí)行所有dirtyComponent的回調(diào)函數(shù)隊(duì)列
總結(jié)
注入的設(shè)計(jì)極大的方便了代碼的維護(hù)慕的,將實(shí)現(xiàn)給原子化阎肝,若以后版本升級(jí),算法更新肮街,可以直接切換injectiion中的原子換成新的版本即可风题。同時(shí),高度抽象的代碼也給閱讀帶來了困難