1、Transation
? ?? ?在上一篇文章中講到在調(diào)用ReactDOM.render方法渲染組件時(shí),其主要功能是通過(guò)ReactMount 文件下的_renderSubtreeIntoContainer方法實(shí)現(xiàn)的,該方法主要將組件渲染分為三個(gè)步驟:
(1) Diff算法判斷新的虛擬DOM差異,首次渲染可以跳過(guò)
(2) 將虛擬DOM實(shí)例化
(3) 將實(shí)例化后的DOM寫入到container中
步驟(3)調(diào)用了ReactUpdates.batchedUpdates方法,它的第一個(gè)參數(shù)是batchedMountComponentIntoNode方法,來(lái)看一看這個(gè)方法的源碼
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(
/* useCreateElement */
!shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement);
transaction.perform(mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context);
ReactUpdates.ReactReconcileTransaction.release(transaction);
}
可見(jiàn),ReactUpdates是通過(guò)調(diào)用ReactUpdates.ReactReconcileTransaction 的transaction.perform()實(shí)現(xiàn)的,為什么調(diào)用perform呢?何為transation?
官方解釋如下:
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. TheTransactionclass 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 aTransaction` for reuse multiple times, that potentially is used to wrap several different methods. Wrappers are extremely simple - they only require implementing two methods.
Transaction對(duì)需要執(zhí)行的方法進(jìn)行封裝,只允許你在當(dāng)前沒(méi)有其他事物被運(yùn)行時(shí)才運(yùn)行當(dāng)前事物。其結(jié)構(gòu)如下:

? ?Transaction將需要執(zhí)行的函數(shù)封裝成兩個(gè)wrapper,每個(gè)wrapper包含了initialize方法和close方法。執(zhí)行一個(gè)transaction其實(shí)就是調(diào)用它的perform,源碼如下:
/* eslint-enable space-before-function-paren */
var errorThrown;
var ret;
try {//標(biāo)志當(dāng)前處于事物正在執(zhí)行 ,將
this._isInTransaction = true;
errorThrown = true;
//事物排隊(duì)
this.initializeAll(0);
// 執(zhí)行method
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
// 執(zhí)行結(jié)束后 close transaction。
try {
if (errorThrown) {
try {
this.closeAll(0);
} catch (err) {}
} else {
this.closeAll(0);
}
} finally {
//將this._isInTransaction設(shè)置為false,結(jié)束當(dāng)前事物,標(biāo)志其他transform可以執(zhí)行。
this._isInTransaction = false;
}
}
return ret;
}
可見(jiàn),transaction的perform方法其實(shí)就是對(duì)call方法進(jìn)行了封裝。
? ?? ?在執(zhí)行transaction時(shí),首先會(huì)先調(diào)用initializeAll()進(jìn)行將需要進(jìn)行的操作加入臨時(shí)隊(duì)列,
initializeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
}
當(dāng)transaction執(zhí)行結(jié)束時(shí)會(huì)調(diào)用close結(jié)束當(dāng)前事物。
closeAll: function (startIndex) {
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;
}
可見(jiàn),batchUpdate 功能都是通過(guò)執(zhí)行各種 transaction 實(shí)現(xiàn)的。當(dāng)虛擬DOM實(shí)例化之后并沒(méi)有立刻插入到DOM中,而是通過(guò) ReactUpdates.batchedUpdate 方法存入臨時(shí)隊(duì)列中。當(dāng)一個(gè) transaction 完成后,才會(huì)context寫到container中。
batchedMountComponentIntoNode(componentInstance, container, shouldReuseMarkup, context)
在React中還有很多地方使用到了transaction,比如this.setState()。就是我們今天的主題:
2、React的更新機(jī)制
? ?? ?React更新機(jī)制來(lái)源于一個(gè)React.js網(wǎng)站React Kung Fu
,在此安利一下,個(gè)人覺(jué)得對(duì)學(xué)習(xí)react很有幫助。
? ?? ?在react需要更新時(shí),通常需要調(diào)用setState(),我們來(lái)看一個(gè)實(shí)例:
var Counter = React.createClass({
getInitialState: function () {
return { clickCount: 0 };
},
handleClick: function () {
this.setState(function(state) {
return {clickCount: state.clickCount + 1};
});
},
render: function () {
return (<h2 onClick={this.handleClick}>點(diǎn)我!點(diǎn)擊次數(shù)為: {this.state.clickCount}</h2>);
}
});
ReactDOM.render(
<Counter />,
document.getElementById('message')
);
在React文件中,其組件來(lái)自ReactBaseClasses文件下的ReactComponent,setState是它的一個(gè)方法。我們來(lái)看一看在ReactBaseClasses文件下的源碼:
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, 'setState');
}
};
? ?? ?setState方法主要做了兩件事情:
一是將setState放入updater的SetState 隊(duì)列;
二是將callback放入updater的Callback隊(duì)列。
? ?? ?在setState()方法中,使用了this.updater對(duì)象,那么什么是updater呢?顧名思義,它是一個(gè)更新作用的對(duì)象,定義在ReactClass 和 ReactComponent中,定義如下:
this.updater = updater || ReactNoopUpdateQueue;
? ?? ?如果沒(méi)有傳入?yún)?shù)updater,那么this.updater的值就是ReactNoopUpdateQueue來(lái)進(jìn)行初始化。而ReactNoopUpdateQueue.enqueueSetState主要起到一個(gè)在非生產(chǎn)版本中警告(warning)的作用。真正的updater是在render中注入(inject)的。因此如果你在constructor中嘗試調(diào)用setState,也會(huì)給出相應(yīng)的警告表明在非安裝或已卸載的組件中不能使用setState。
2-1 updater
? ?? ?那么updater是如何注入的呢?在React Kung Fu網(wǎng)有這么一句話:
React.js codebase relies heavily on a dependency injection principle. This allows to substitute parts of React.js based on the environment (server-side vs. client-side, different platforms) in which you’re rendering. ReactComponent is a part of the isomorphic namespace - it will always exist, no matter it is React Native, ReactDOM on browser or server-side. Also it contains only pure JavaScript which should run on every device capable of understanding the ECMAScript 5 standard of JS.
? ?? ?React.js的源碼大量地依賴于注入原則,實(shí)現(xiàn)在其他平臺(tái)環(huán)境的渲染。ReactComponent腳本存在于isomorphic目錄這意味著它支持異構(gòu),即它可用于React Native,在瀏覽器端或服務(wù)器端運(yùn)行的ReactDOM。那么真實(shí)的updater在哪里注入的呢?
* 初始化組件, 渲染層和注冊(cè)事件監(jiān)聽(tīng)器。
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {?object} hostParent
* @param {?object} hostContainerInfo
* @param {?object} context
* @return {?string} Rendered markup to be inserted into the DOM.
* @final
* @internal
*/
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
…
var updateQueue = transaction.getUpdateQueue();
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
inst.props = publicProps;
inst.context = publicContext;
inst.refs = emptyObject;
inst.updater = updateQueue;
}
_constructComponent方法的返回值是同文件下_constructComponentWithoutOwner的返回值:
_constructComponent: function (doConstruct, publicProps, publicContext, updateQueue){
…
return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
}
functio_constructComponentWithoutOwner:function (doConstruct, publicProps, publicContext, updateQueue) {
…
return new Component(publicProps, publicContext, updateQueue);
…
}
? ?? ?由此可見(jiàn),更新隊(duì)列updateQueue是在_constructComponentWithoutOwner方法中注入?,F(xiàn)在知道了何為updater,接下來(lái)我們回歸到setState中的兩個(gè)回調(diào)方法enqueueSetState和enqueueCallback。
未完待續(xù)。。。
繼續(xù)。。。
2-2 enqueueSetState和enqueueCallback
? ?? ?在ReactUpdateQueue.js中找到這兩個(gè)方法的源碼:
enqueueSetState: function (publicInstance, partialState) {
… …
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
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);
},
? ?? ?從上面兩個(gè)函數(shù)可以發(fā)現(xiàn),他們都使用了enqueueUpdate函數(shù),這兩個(gè)函數(shù)的邏輯如下:
? ?? ?(1) 創(chuàng)建對(duì)象internalInstance,它是getInternalInstanceReadyForUpdate的實(shí)例對(duì)象。
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
var internalInstance = ReactInstanceMap.get(publicInstance);
}
? ?? ?由此可見(jiàn)internalInstance其實(shí)是ReactInstanceMap的實(shí)例,getInternalInstanceReadyForUpdate只是起到委托的作用。而ReactInstanceMap 是一個(gè)操作實(shí)例對(duì)象的函數(shù)封裝。注:state初始化時(shí)會(huì)調(diào)用ReactInstanceMap.set方法
set: function (key, value) {
key._reactInternalInstance = value;
}
在更新隊(duì)列時(shí),用get方法取其值。初次之外還有remove和has方法。
? ?? ?(2) 對(duì)internalInstance進(jìn)行修改,將setState寫入internalInstance._pendingStateQueue隊(duì)列中,將callback寫入_pendingCallbacks。
? ?? ?(3) 最后調(diào)用enqueueUpdate(internalInstance)刷新更新。
來(lái)看一看enqueueUpate是如何實(shí)現(xiàn)刷新更新的:
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
因此,由于ReactUpdate是中有共享方法,而它得問(wèn)依賴是被注入的。
? ?? ?enqueueUpate是如何通過(guò)引用ReactUpdates.enqueueUpdate方法實(shí)現(xiàn)flush更新的。在ReactUpdates.js下找到enqueue的源碼有:
/**
* Mark a component as needing a rerender, adding an optional callback to a
* list of functions which will be executed once the rerender occurs.
*/
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
// 如果當(dāng)前沒(méi)有分批處理操作則使用batchingStrategy.batchedUpdates分批處理更新隊(duì)列,結(jié)束后返回
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 如果當(dāng)前有分批處理操作,則把需要更新的組件加入dirtyComponents隊(duì)列中
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
enqueueUpdate的功能實(shí)現(xiàn)由兩個(gè)重要的步驟,分別是ensureInjected()和
batchingStrategy(一種批量處理更新機(jī)制的策略)。
ensureInjected的源碼如下:
function ensureInjected() {
!(ReactUpdates.ReactReconcileTransaction && batchingStrategy) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'ReactUpdates: must inject a reconcile transaction class and batching strategy') : _prodInvariant('123') : void 0;
}
? ?? ?由ensureInjected的代碼邏輯可知,ReactUpdate必須要注入ReactUpdates.ReactReconcileTransaction(一個(gè)解調(diào)的事物類)和batchingStrategy(批量處理策略)。BatchingStrategy是一種React批量處理更新的策略。在源碼中,當(dāng)且僅有一個(gè)策略是ReactDefaultBatchingStrategy。ReactReconcileTransaction 依賴于環(huán)境,負(fù)責(zé)處理更新后的事物狀態(tài),比如在DOM中,修復(fù)更新后導(dǎo)致文本選擇狀態(tài)的丟失問(wèn)題、在解調(diào)期間禁止事件和生命周期的方法進(jìn)入隊(duì)列。
? ?? ?enqueueUpdate的有些難理解,特別是第一眼好像并沒(méi)有發(fā)生特別的事情。BatchingStrategy 能告訴你當(dāng)前是否有transaction處于進(jìn)程中。如果不在,enqueueUpdate會(huì)停下來(lái),將它自身注冊(cè)到transaction中并執(zhí)行。然后一個(gè)組件會(huì)被添加到dirty組件列表中。但是到目前為止,還沒(méi)有搞清楚狀態(tài)是什么致使?fàn)顟B(tài)更新的。為了理解這個(gè)過(guò)程是如何發(fā)生的,我們必須要搞清楚batchingStrategy是從哪里注入的,傳入了什么參數(shù)。
? ?? ?我們從ReactDOM入口文件開(kāi)始找inject,在該文件require文件下的第一行,有ReactDefaultInjection.inject();找到ReactDefaultInjection文件,在它的更新屬性中有
ReactInjection.Updates.injectReconcileTransaction(ReactReconcileTransaction);
ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);
找到ReactInjection,我們來(lái)看一看它是定義update:
var ReactInjection = {
Updates: ReactUpdates.injection
};
繼續(xù)向下ReactUpdates.js文件
var ReactUpdates = {
injection: ReactUpdatesInjection,
};
var ReactUpdatesInjection = {
injectBatchingStrategy: function (_batchingStrategy) {
batchingStrategy = _batchingStrategy;
}
}
? ?? ?到此為止,我們知道ReactInjection.Updates = ReactUpdatesInjection .injectReconcileTransaction;現(xiàn)在來(lái)看傳入到其中的參數(shù)_batchingStrategy為何物,也就是ReactDefaultInjection文件中的傳入的參數(shù)ReactDefaultBatchingStrategy。ReactDefaultBatchingStrategy中有兩個(gè)封裝對(duì)象RESET_BATCHED_UPDATES 和FLUSH_BATCHED_UPDATES 。
? ?? ?(1) RESET_BATCHED_UPDATES:負(fù)責(zé)在事物結(jié)束后清理isBatchingUpdates的標(biāo)志位;
? ?? ?(2) FLUSH_BATCHED_UPDATES:
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
? ?? ?會(huì)在事物結(jié)束后調(diào)用來(lái)自ReactUpdates一個(gè)方法flushBatchedUpdates,它是狀態(tài)更新的核心代碼。我們來(lái)看一看了flushBatchedUpdates是如何實(shí)現(xiàn)狀態(tài)更新的,在ReactUpdates文件下找到flushBatchedUpdates的定義
var flushBatchedUpdates = function () {
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// array and perform any updates enqueued by mount-ready handlers (i.e.,
// componentDidUpdate) but we need to check here too in order to catch
// updates enqueued by setState callbacks and asap calls.
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);
}
}
};
? ?? ?在此有出現(xiàn)了一個(gè)新的事物ReactUpdatesFlushTransaction,它主要用來(lái)捕獲在運(yùn)行flushBatchedUpdate后將要運(yùn)行的updates。這個(gè)過(guò)程比較復(fù)雜,因?yàn)閏omponentDidUpdate或則setState后的回調(diào)方法需要進(jìn)入下一個(gè)更新隊(duì)列。另外這個(gè)事物是getpooled來(lái)的,而不是實(shí)時(shí)創(chuàng)建的,這樣做的好處是避免不必要的垃圾收集。另外這個(gè)地方也涉及到asp update的內(nèi)容,后續(xù)將介紹到。
未完待續(xù)。。。