英文注釋翻譯
React 中的 Transaction 創(chuàng)建一個(gè)黑盒環(huán)境來對方法進(jìn)行封裝侣夷,它能夠封裝任何方法,以便在調(diào)用方法之前和之后維護(hù)某些不變量(即使在調(diào)用封裝的方法時(shí)拋出異常)。
任何實(shí)例化事務(wù)的人都可以在創(chuàng)建時(shí)提供不變量的實(shí)施者恬试∷裥觯“Transaction”類本身將為您提供一個(gè)額外的自動(dòng)不變量(_isInTransaction)——任何事務(wù)實(shí)例在已經(jīng)運(yùn)行時(shí)不應(yīng)該運(yùn)行這個(gè)不變量。您通常會(huì)創(chuàng)建一個(gè)“事務(wù)”的單一實(shí)例推正,以便多次重用恍涂,該實(shí)例可能用于包裝幾個(gè)不同的方法。包裝器非常簡單——它們只需要實(shí)現(xiàn)兩個(gè)方法植榕。
使用范圍
- 保存‘調(diào)和’前后的輸入選擇范圍再沧。恢復(fù)選擇尊残,即使在發(fā)生意外錯(cuò)誤的情況下炒瘸。
- 重排Dom時(shí),禁用事件寝衫,避免引發(fā)多余的 blur/focus 事件顷扩,同時(shí)確保重排之后重新激活事件系統(tǒng)
- work線程調(diào)和后,由主線程更新UI
- 渲染完成之后慰毅,調(diào)用所有的componentDidUpdate
- (Future case)包裝“ReactWorker”隊(duì)列的特定刷新隘截,以保存“scrollTop”(自動(dòng)滾動(dòng)感知DOM)。
- (Future case)DOM更新前后的布局計(jì)算汹胃。
事務(wù)性插件API
- 'initialize': 具有“初始化”方法的模塊婶芭,該方法返回任何預(yù)計(jì)算。
- 'close': 以及接受預(yù)計(jì)算的“close”方法统台〉窭蓿“close”在包裝的流程完成或失敗時(shí)調(diào)用。
自定義Transaction: 通過將Tracaction中的屬性通過Object.assign添加到自定義transaction的原型鏈中
個(gè)人理解
Transaction 對我們所要執(zhí)行的方法進(jìn)行一個(gè)封裝贱勃,創(chuàng)建了一個(gè)黑盒環(huán)境井赌。每個(gè) Transaction 都會(huì)被一個(gè)或多個(gè) wrapper 包裹,每一個(gè) wrapper 都包含兩個(gè)屬性贵扰,一個(gè)是
initialize
一個(gè)是close
仇穗,當(dāng) transaction執(zhí)行的時(shí)候,首先會(huì)執(zhí)行所有的initialize
并添加異常機(jī)制戚绕,之后執(zhí)行我們包裝的方法纹坐,最后在執(zhí)行所有的close
并添加異常機(jī)制。屬性概述
_isInTransaction
:Transaction是否在運(yùn)行
reinitializeTransaction
:初始化Transaction
getTransactionWrappers
:獲取包裹的wrapper
isInTransaction
:判斷當(dāng)前是否在運(yùn)行的方法
perform
:Transaction的核心方法舞丛,用來執(zhí)行包裹的method
inistializeAll
:執(zhí)行所有wrapper
的initialize
closeAll
:執(zhí)行所有wrapper
的close
自定義Transaction
當(dāng)我們衍生出一個(gè)自定義Transaction時(shí)耘子,只需要使用Object.assign將Transaction的所有方法添加到我們自定義的Transaction上果漾,然后重寫
getTransactionWrappers
方法。
官方給的一個(gè)圖很形象
wrappers
是在Transaction被創(chuàng)建時(shí)注入的谷誓,實(shí)質(zhì)上是通過上面說的通過Object.assign方法重寫getTransactionWrappers
方法得到包裹的wrappers
绒障,當(dāng)運(yùn)行perform
方法時(shí),首先執(zhí)行的是所有的initialize
之后是method
最后是所有的close
Transaction
reinitializeTransaction
用于 Transaction 初始化捍歪,在實(shí)例化Transaction時(shí)户辱,都執(zhí)行了這個(gè)方法。
// Transaction.js
reinitializeTransaction: function () {
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
通過調(diào)用getTransactionWrappers
方法糙臼,給Transaction實(shí)例添加transactionWrappers
屬性庐镐,之后附加wrapperInitData
為空數(shù)組,并將運(yùn)行標(biāo)志_isInTransaction
設(shè)為false
变逃。
其中wrapperInitData
數(shù)組是用來存儲(chǔ)initialize
的返回值的必逆,用原文注釋中的說法就是,存儲(chǔ)initialize
返回的預(yù)計(jì)算數(shù)據(jù)韧献,在close
方法中需要用到這些預(yù)計(jì)算末患,實(shí)質(zhì)上就是用來判斷是否出現(xiàn)了異常。
getTransactionWrappers
// Transaction.js
/**
* @abstract
* @return {Array<TransactionWrapper>} Array of transaction wrappers.
*/
getTransactionWrappers: null,
這個(gè)屬性用來獲取Transaction外部的wrappers
锤窑,在定義Transaction時(shí)璧针,會(huì)通過Object.assign方法重寫該屬性
isInTransaction
// Transaction.js
isInTransaction: function () {
return !!this._isInTransaction;
},
判斷當(dāng)前Transaction
是否在執(zhí)行,使用!!
兩個(gè)感嘆號渊啰,是可以做類型判斷
perform
// Transaction.js
perform: function (method, scope, a, b, c, d, e, f) {
/* eslint-enable space-before-function-paren */
!!this.isInTransaction() ? /**/
var errorThrown;
var ret;
try {
this._isInTransaction = true;
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// one of these calls threw.
// 先設(shè)置為 true 在調(diào)用 close 之后設(shè)置 false探橱,如果最后還是 true 的話 那么中途出現(xiàn)了異常
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
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.
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
Transaction
的核心方法,用來執(zhí)行包裹的method
绘证。首先會(huì)判斷當(dāng)前
Transaction
是否在運(yùn)行隧膏,是的話會(huì)報(bào)錯(cuò)
之后會(huì)將_isInTransaction
設(shè)為true
,表明該Transaction在執(zhí)行
調(diào)用
initializeAll(0)
參數(shù)0
表示從第一個(gè)wrapper開始運(yùn)行嚷那,0
表示wrappers數(shù)組的下標(biāo)errorThrown
用來判斷執(zhí)行method
期間是否出現(xiàn)了異常執(zhí)行
method
執(zhí)行所有的
close
將
_isInTransaction
設(shè)為false
表明Transaction
執(zhí)行完成返回
method
的返回值
initializeAll
// Transaction.js
initializeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
// Catching errors makes debugging more difficult, so we start with the
// OBSERVED_ERROR state before overwriting it with the real return value
// of initialize -- if it's still set to OBSERVED_ERROR in the finally
// block, it means wrapper.initialize threw.
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
// The initializer for wrapper i threw an error; initialize the
// remaining wrappers but silence any exceptions from them to ensure
// that the first error is the one to bubble up.
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
執(zhí)行所有的initialize
方法胞枕,這邊做了異常處理,異常邏輯為:將對應(yīng)的wrapperInitData
設(shè)為OBSERVED_ERROR
,之后將initialize
的返回值設(shè)為新的wrapperInitData
魏宽,最后判斷wrapperInitData
的值腐泻,如果仍為OBSERVED_ERROR
表明在執(zhí)行initialize的過程中發(fā)生了異常。
closeAll
執(zhí)行所有的close队询,其中的異常處理機(jī)制和initializeAll
相類似
closeAll: function (startIndex) {
!this.isInTransaction() ? /**/
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// wrapper.close threw.
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
// The closer for wrapper i threw an error; close the remaining
// wrappers but silence any exceptions from them to ensure that the
// first error is the one to bubble up.
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}
執(zhí)行完之后將wrapperInitData
清空派桩。
ReactDefaultBatchingStrategyTransaction
在ReactDefaultBatchingStrategy.js
中定義了一個(gè)默認(rèn)批處理策略事務(wù)。
// ReactDefaultBatchingStrategy.js
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
});
通過Object.assign
將Transaction的屬性添加到自定義Transaction的原型鏈中蚌斩,并重寫getTransactionWrappers
方法铆惑,并且在實(shí)例化時(shí),都會(huì)調(diào)用reinitializeTransaction
方法進(jìn)行初始化,這就是自定義一個(gè)Transaction的過程员魏。
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];
這個(gè)Transaction被兩個(gè)wrapper包裹丑蛤。他們的initialize
方法都是空函數(shù),那么關(guān)注點(diǎn)就轉(zhuǎn)移到close
方法逆趋,在該Transaction方法直接完之后盏阶,首先會(huì)將ReactDefaultBatchingStrategy.isBatchingUpdates
設(shè)為false
晒奕,之后就調(diào)用ReactUpdates.flushBatchedUpdates
刷新更新隊(duì)列闻书。
ReactReconcileTransaction
調(diào)和事務(wù)
/**
* Currently:
* - The order that these are listed in the transaction is critical:
* - Suppresses events.
* - Restores selection range.
*
* Future:
* - Restore document/overflow scroll positions that were unintentionally
* modified via DOM insertions above the top viewport boundary.
* - Implement/integrate with customized constraint based layout system and keep
* track of which dimensions must be remeasured.
*
* @class ReactReconcileTransaction
*/
function ReactReconcileTransaction(useCreateElement) {
this.reinitializeTransaction();
// Only server-side rendering really needs this option (see
// `ReactServerRendering`), but server-side uses
// `ReactServerRenderingTransaction` instead. This option is here so that it's
// accessible and defaults to false when `ReactDOMComponent` and
// `ReactDOMTextComponent` checks it in `mountComponent`.`
this.renderToStaticMarkup = false;
this.reactMountReady = CallbackQueue.getPooled(null);
this.useCreateElement = useCreateElement;
}
構(gòu)造函數(shù)內(nèi)部包含了三個(gè)屬性
renderToStaticMarkup
:只有在服務(wù)器端運(yùn)行時(shí)用到reactMountReady
:通過CallbackQueue.getPooled
獲取,實(shí)質(zhì)上返回一個(gè)CallbackQueue
實(shí)例useCreateElement
:值為構(gòu)造函數(shù)實(shí)例化時(shí)傳入的參數(shù)
調(diào)和事務(wù)的屬性稍微多了一點(diǎn)脑慧,通過混合Mixin
注入原型鏈
var Mixin = {
/**
* @see Transaction
* @abstract
* @final
* @return {array<object>} List of operation wrap procedures.
* TODO: convert to array<TransactionWrapper>
*/
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
},
/**
* @return {object} The queue to collect `onDOMReady` callbacks with.
*/
getReactMountReady: function () {
return this.reactMountReady;
},
/**
* @return {object} The queue to collect React async events.
*/
getUpdateQueue: function () {
return ReactUpdateQueue;
},
/**
* Save current transaction state -- if the return value from this method is
* passed to `rollback`, the transaction will be reset to that state.
*/
checkpoint: function () {
// reactMountReady is the our only stateful wrapper
return this.reactMountReady.checkpoint();
},
rollback: function (checkpoint) {
this.reactMountReady.rollback(checkpoint);
},
/**
* `PooledClass` looks for this, and will invoke this before allowing this
* instance to be reused.
*/
destructor: function () {
CallbackQueue.release(this.reactMountReady);
this.reactMountReady = null;
}
};
這邊提到了ReactUpdateQueue
魄眉,也是一個(gè)重點(diǎn)。
外部包含了三個(gè)wrapper
var TRANSACTION_WRAPPERS = [SELECTION_RESTORATION, EVENT_SUPPRESSION, ON_DOM_READY_QUEUEING];
// 確保下拉框選擇的數(shù)據(jù)在transaction后仍被選擇
var SELECTION_RESTORATION = {
/**
* @return {Selection} Selection information.
*/
initialize: ReactInputSelection.getSelectionInformation,
/**
* @param {Selection} sel Selection information returned from `initialize`.
*/
close: ReactInputSelection.restoreSelection
};
// 抑制可能由于高級DOM操作(如臨時(shí)從DOM中刪除文本輸入)而意外分派的事件(blur/focus)闷袒。
var EVENT_SUPPRESSION = {
/**
* @return {boolean} The enabled status of `ReactBrowserEventEmitter` before
* the reconciliation.
*/
initialize: function () {
var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
ReactBrowserEventEmitter.setEnabled(false);
return currentlyEnabled;
},
/**
* @param {boolean} previouslyEnabled Enabled status of
* `ReactBrowserEventEmitter` before the reconciliation occurred. `close`
* restores the previous value.
*/
close: function (previouslyEnabled) {
ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
}
};
// 提供一個(gè)隊(duì)列坑律,來收集在 componentDidMount 和 componentDidUpdate 回調(diào)
// 在執(zhí)行事務(wù)期間,遇到這兩個(gè)生命周期囊骤,會(huì)將回調(diào)押入到隊(duì)列中晃择。在close的時(shí)候在去執(zhí)行
var ON_DOM_READY_QUEUEING = {
/**
* Initializes the internal `onDOMReady` queue.
*/
initialize: function () {
this.reactMountReady.reset();
},
/**
* After DOM is flushed, invoke all registered `onDOMReady` callbacks.
*/
close: function () {
this.reactMountReady.notifyAll();
}
};
_assign(ReactReconcileTransaction.prototype, Transaction, Mixin);
PooledClass.addPoolingTo(ReactReconcileTransaction);
再往下就是這兩句,第一句是將Transaction
,Mixin
都添加到原型鏈中也物。
第二句是調(diào)用PooledClass.addPoolingTo
用到了PoolClass
屬性宫屠。
PoolClass
PoolClass
主要目的就是減少內(nèi)存消耗。
// PoolClass.js
var PooledClass = {
addPoolingTo: addPoolingTo,
oneArgumentPooler: oneArgumentPooler,
twoArgumentPooler: twoArgumentPooler,
threeArgumentPooler: threeArgumentPooler,
fourArgumentPooler: fourArgumentPooler
};
// PoolClass.js
var addPoolingTo = function (CopyConstructor, pooler) {
// Casting as any so that flow ignores the actual implementation and trusts
// it to match the type we declared
var NewKlass = CopyConstructor;
NewKlass.instancePool = [];
NewKlass.getPooled = pooler || DEFAULT_POOLER;
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
NewKlass.release = standardReleaser;
return NewKlass;
};
通過addPoolingTo
方法給傳入的構(gòu)造函數(shù)添加instancePool
屬性和getPooled
滑蚯,以及一個(gè)釋放池的函數(shù)NewKlass.release = standardReleaser
浪蹂,默認(rèn)池大小為10。默認(rèn)的getPooled
是帶一個(gè)參數(shù)的池函數(shù)告材。PoolClass提供了很多個(gè)池函數(shù)坤次,他們的區(qū)別就是參數(shù)的多少,默認(rèn)是一個(gè)參數(shù)的斥赋。
// PoolClass.js
var oneArgumentPooler = function (copyFieldsFrom) {
var Klass = this;
if (Klass.instancePool.length) {
var instance = Klass.instancePool.pop();
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom);
}
};
這邊可以看到如果池子里有的話缰猴,那么就直接彈出一個(gè)然后重新實(shí)例化,沒有的話則重新創(chuàng)建疤剑。使用池來儲(chǔ)存滑绒,減少內(nèi)存消耗。
所以這邊看到getPooled
方法那就是返回一個(gè)實(shí)例調(diào)用者的實(shí)例骚露。
ReactUpdatesFlushTransaction
更新刷新事務(wù)蹬挤,定義在ReactUpdates.js
中,在執(zhí)行ReactUpdates.flushBatchedUpdates時(shí)開啟棘幸。
// ReactUpdates.js
function ReactUpdatesFlushTransaction() {
this.reinitializeTransaction();
this.dirtyComponentsLength = null;
this.callbackQueue = CallbackQueue.getPooled();
this.reconcileTransaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
}
_assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
},
destructor: function () {
this.dirtyComponentsLength = null;
CallbackQueue.release(this.callbackQueue);
this.callbackQueue = null;
ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
this.reconcileTransaction = null;
},
perform: function (method, scope, a) {
// Essentially calls `this.reconcileTransaction.perform(method, scope, a)`
// with this transaction's wrappers around it.
return Transaction.perform.call(this, this.reconcileTransaction.perform, this.reconcileTransaction, method, scope, a);
}
});
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
需要注意的是這邊重寫了perform
方法焰扳。并且使用PoolClass.addPoolingTo
添加了池屬性。
外部包裹了兩個(gè)wrapper
var NESTED_UPDATES = {
initialize: function () {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function () {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
// Additional updates were enqueued by componentDidUpdate handlers or
// similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
// these new updates so that if A's componentDidUpdate calls setState on
// B, B will update before the callback A's updater provided when calling
// setState.
// 額外的更新是由于 componentDidUpdate 或其他類似的操作。
// 在 update_queue wrapper close之前 我們希望運(yùn)行這些新的更新以至于 如果 A 的componenDidUpdate 調(diào)用 B的 setState
// 那么 B 會(huì)在 A 的updater提供的回調(diào)之前更新
//
// 在我們自己的UPDATE_QUEUEING包裝器關(guān)閉之前吨悍,我們希望運(yùn)行這些新更新
// 以便如果A的componentDidUpdate在B上調(diào)用setState扫茅,那么B將在調(diào)用setState時(shí)提供回調(diào)A的updater之前更新。
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates();
} else {
dirtyComponents.length = 0;
}
}
};
var UPDATE_QUEUEING = {
initialize: function () {
this.callbackQueue.reset();
},
close: function () {
this.callbackQueue.notifyAll();
}
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
需要注意的是育瓜。在第一個(gè)wrapperNESTED_UPDATES
中的initialize
函數(shù)葫隙,初始化當(dāng)前實(shí)例的dirtyComponentsLength = dirtyComponents.length
,之后在close
的時(shí)候躏仇,發(fā)現(xiàn)他們兩長度不一樣了恋脚,說明在執(zhí)行method
時(shí)引起了額外的更新。這個(gè)時(shí)候就需要刷新這些新的更新焰手。
而第二wrapperUPDATE_QUEUEING
在會(huì)close的時(shí)候執(zhí)行callbackQueue.notifyAll
糟描,這個(gè)方法是用來之前收集到的所有的回調(diào)。
這三個(gè)事務(wù)時(shí)目前我看到的三個(gè)书妻,后面如果有其他的我會(huì)在加以分析船响。
總結(jié)
這一篇是介紹 React 中的Transaction, 以及目前三種自定義的事務(wù)內(nèi)部的一些屬性及包裹的wrapper