【譯】了解React源代碼-初始渲染(簡單組件)1
【譯】了解React源代碼-初始渲染(簡單組件)2
【譯】了解React源代碼-初始渲染(簡單組件)3
【譯】了解React源代碼-初始渲染(類組件)4
【譯】了解React源代碼-初始渲染(類組件)5
【譯】了解React源代碼-UI更新(事務(wù))6
【譯】了解React源代碼-UI更新(事務(wù))7
【譯】了解React源代碼-UI更新(單個DOM)8
【譯】了解React源代碼-UI更新(DOM樹)9
在某種程度上,復(fù)雜而有效的用戶界面更新是使React React的原因。 但是,在我們深入研究能夠?qū)崿F(xiàn)UI更新的眾所周知的機制(虛擬DOM和差異算法)之前,我們需要了解Transaction,它將控制權(quán)從高級API setState()轉(zhuǎn)移到那些底層處理邏輯。
本文中使用的文件:
renderers / shared / utils / Transaction.js:
定義核心Transaction類
renderers / shared / stack / reconciler / ReactDefaultBatchingStrategy.js:
定義ReactDefaultBatchingStrategyTransaction及其API包裝器ReactDefaultBatchingStrategy
renderers / shared / stack / reconciler / ReactUpdates.js:
定義使用ReactDefaultBatchingStrategy的enqueueUpdate()
Unlike the previous posts that start from everyday APIs and move down the call stack. This post will take a bottom up approach.
與以前的文章不同,這些文章從日常的API開始并向下移動到調(diào)用棧。 這篇文章將采取自下而上的方法。
首先,我們來看一下
事務(wù)核心類
此類中唯一的事實上的“公共”方法是perform,它也提供其核心功能:
...
/**
...
*
* @param {function} method Member of scope to call.
* @param {Object} scope Scope to invoke from.
* @param {Object?=} a Argument to pass to the method.
* @param {Object?=} b Argument to pass to the method.
* @param {Object?=} c Argument to pass to the method.
* @param {Object?=} d Argument to pass to the method.
* @param {Object?=} e Argument to pass to the method.
* @param {Object?=} f Argument to pass to the method.
*
* @return {*} Return value from `method`.
*/
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 {
/* eslint-enable space-before-function-paren */
...
var errorThrown;
var ret;
try {
this._isInTransaction = true;
...
// one of these calls threw.
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
...
try {
this.closeAll(0);
} catch (err) {}
} else {
...
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
...
TransactionImpl@renderers/shared/utils/Transaction.js
除了調(diào)用作為第一個參數(shù)傳遞給它的回調(diào)方法外,perform()只是1)在回調(diào)之前調(diào)用initializeAll(),然后在2)之后調(diào)用closeAll()。
在這里,errorThrown用來指示method.call()中發(fā)生的異常,在這種情況下,邏輯直接跳轉(zhuǎn)到finally塊,然后將errorThrown設(shè)置為false。
接下來,我們來看一下perform()之前和之后調(diào)用的兩個方法的實現(xiàn):
...
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;
this.wrapperInitData[i] = wrapper.initialize
? wrapper.initialize.call(this)
: null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
...
closeAll: function(startIndex: number): void {
...// scr: sanity check
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;
TransactionImpl@renderers/shared/utils/Transaction.js
這兩個方法只是簡單地迭代this.transactionWrappers并分別調(diào)用其initialize()和close()。
this.transactionWrappers在Transaction的默認構(gòu)造函數(shù)中使用this.getTransactionWrappers()初始化:
...
reinitializeTransaction: function(): void {
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
...
TransactionImpl@renderers/shared/utils/Transaction.js
我們將很快看到this.transactionWrappers到底是什么。
這里的異常處理細節(jié)有些有趣。 以initializeAll()作為實例。 如果initialize()中發(fā)生異常,則finally塊(而不是catch)將處理其余this.transactionWrappers(即,從i +1到transactionWrappers.length-1)的initialize()。 然后,異常會中斷for循環(huán)和整個initializeAll()邏輯,并一直處理到perform()(initializeAll()的調(diào)用者)內(nèi)的finally塊,從而有效地跳過了
ret = method.call(scope, a, b, c, d, e, f);
在異常初始化的情況下。 最后,在相同的finally塊中調(diào)用closeAll()以完成事務(wù)。
現(xiàn)在我們知道本質(zhì)上什么是Transaction,但是它的作用是什么? 為了回答這個問題,我們以Transaction實例化為例,它是UI更新的事務(wù)入口點。
ReactDefaultBatchingStrategyTransaction
首先,ReactDefaultBatchingStrategyTransaction是實現(xiàn)getTransactionWrappers()的Transaction的子類:
...
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
...
ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
其次,TRANSACTION_WRAPPERS是this.transactionWrappers的來源,它為上一節(jié)中使用的perform()提供了pre(initialize())和post(close())函數(shù)。
...
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
}; // scr: -----------------------------------------------------> 2)
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
}; // scr: -----------------------------------------------------> 2)
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; // scr: -------------------------------> 2)
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
} // scr: ------------------------------------------------------> 1)
...
// scr: ------------------------------------------------------> 3)
var transaction = new ReactDefaultBatchingStrategyTransaction();
...
ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
1)在ReactDefaultBatchingStrategyTransaction的構(gòu)造函數(shù)中,調(diào)用超類Transaction的構(gòu)造函數(shù),該構(gòu)造函數(shù)使用2)中定義的FLUSH_BATCHED_UPDATES初始化this.transactionWrappers。
2)定義兩個包裝器以及它們各自的initialize()和close(),它們在迭代FLUSH_BATCHED_UPDATES的循環(huán)中用在Transaction.initializeAll()和Transaction.closeAll()中
3)將ReactDefaultBatchingStrategyTransaction定義為單例。
最后,我們看一下ReactDefaultBatchingStrategy提供的公共API,可以從外界調(diào)用它
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
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) { // scr: --------> not applied here
return callback(a, b, c, d, e);
} else {
return transaction.perform(callback, null, a, b, c, d, e);
}
},
};
ReactDefaultBatchingStrategy@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
將 ReactDefaultBatchingStrategy作為batchingStrategy注入{第二篇 * 5}到ReactUpdates。 ReactUpdates.enqueueUpdate()使用ReactDefaultBatchingStrategy.batchedUpdates(),UI更新入口點setState()的基礎(chǔ)方法。
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) { // scr: ----------> {a}
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// scr: ----------------------------------------------------->
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
// scr: this field is used for sanity check later
component._updateBatchNumber = updateBatchNumber + 1;
}
}
ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js
這是我們在上一篇文章中看到的類似的遞歸技巧。
1)第一次輸入該方法時,ReactDefaultBatchingStrategy.isBatchingUpdates為false,這會觸發(fā)分支{a},該分支通向ReactDefaultBatchingStrategy.batchedUpdates();。
2)batchedUpdates()將ReactDefaultBatchingStrategy.isBatchingUpdates設(shè)置為true,并初始化transaction;
3)batchedUpdates的callback參數(shù)是enqueueUpdate()本身,因此enqueueUpdate將再次使用transaction.perform()再次輸入。 請注意,兩個包裝器的前置方法(initialize())都是emptyFunction,因此兩次調(diào)用enqueueUpdate()之間什么都沒有發(fā)生。
4)當?shù)诙屋斎?code>enqueueUpdate()時(在剛剛初始化的Transaction上下文中),執(zhí)行分支;
...
dirtyComponents.push(component);
...
5)在enqueueUpdate()返回FLUSH_BATCHED_UPDATES的后方法(close())之后; 這是處理前面步驟中標記的所有dirtyComponents的主要方法
*8 we will come back to this FLUSH_BATCHED_UPDATES.close() and ReactUpdates.flushBatchedUpdates() in the next post
* 8我們將在下一篇文章中回到FLUSH_BATCHED_UPDATES.close()和ReactUpdates.flushBatchedUpdates()
6)最后,調(diào)用RESET_BATCHED_UPDATES的后方法(close()),這會將ReactDefaultBatchingStrategy.isBatchingUpdates設(shè)置為false并完成循環(huán)。
重要的是要注意,應(yīng)該在ReactDefaultBatchingStrategy.isBatchingUpdates:false的上下文中執(zhí)行3)和6)之間對enqueueUpdate()的任何后續(xù)調(diào)用,這意味著在這種情況下將使用分支。 所以就像
->dirtyComponents.push(component);
->dirtyComponents.push(component);
->dirtyComponents.push(component);
...
----->ReactUpdates.flushBatchedUpdates
Wrap-up

(原文鏈接Understanding The React Source Code - UI Updating (Transaction) VI)