【译】了解React源代码-初始渲染(简单组件)1
【译】了解React源代码-初始渲染(简单组件)2
【译】了解React源代码-初始渲染(简单组件)3
【译】了解React源代码-初始渲染(类组件)4
【译】了解React源代码-初始渲染(类组件)5
【译】了解React源代码-UI更新(事务)6
【译】了解React源代码-UI更新(事务)7
【译】了解React源代码-UI更新(单个DOM)8
【译】了解React源代码-UI更新(DOM树)9
在某种程度上,复杂而有效的用户界面更新是使React React的原因。 但是,在我们深入研究能够实现UI更新的众所周知的机制(虚拟DOM和差异算法)之前,我们需要了解Transaction
,它将控制权从高级API setState()
转移到那些底层处理逻辑。
本文中使用的文件:
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开始并向下移动到调用栈。 这篇文章将采取自下而上的方法。
首先,我们来看一下
事务核心类
此类中唯一的事实上的“公共”方法是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
除了调用作为第一个参数传递给它的回调方法外,perform()
只是1)在回调之前调用initializeAll()
,然后在2)之后调用closeAll()
。
在这里,errorThrown
用来指示method.call()
中发生的异常,在这种情况下,逻辑直接跳转到finally
块,然后将errorThrown
设置为false
。
接下来,我们来看一下perform()
之前和之后调用的两个方法的实现:
...
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
并分别调用其initialize()
和close()
。
this.transactionWrappers
在Transaction
的默认构造函数中使用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
到底是什么。
这里的异常处理细节有些有趣。 以initializeAll()
作为实例。 如果initialize()
中发生异常,则finally
块(而不是catch
)将处理其余this.transactionWrappers
(即,从i +1
到transactionWrappers.length-1
)的initialize()
。 然后,异常会中断for
循环和整个initializeAll()
逻辑,并一直处理到perform()
(initializeAll()
的调用者)内的finally
块,从而有效地跳过了
ret = method.call(scope, a, b, c, d, e, f);
在异常初始化的情况下。 最后,在相同的finally
块中调用closeAll()
以完成事务。
现在我们知道本质上什么是Transaction
,但是它的作用是什么? 为了回答这个问题,我们以Transaction
实例化为例,它是UI更新的事务入口点。
ReactDefaultBatchingStrategyTransaction
首先,ReactDefaultBatchingStrategyTransaction
是实现getTransactionWrappers()
的Transaction
的子类:
...
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
...
ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
其次,TRANSACTION_WRAPPERS
是this.transactionWrappers
的来源,它为上一节中使用的perform()
提供了pre(initialize()
)和post(close()
)函数。
...
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
的构造函数中,调用超类Transaction
的构造函数,该构造函数使用2)中定义的FLUSH_BATCHED_UPDATES
初始化this.transactionWrappers
。
2)定义两个包装器以及它们各自的initialize()
和close()
,它们在迭代FLUSH_BATCHED_UPDATES
的循环中用在Transaction.initializeAll()
和Transaction.closeAll()
中
3)将ReactDefaultBatchingStrategyTransaction
定义为单例。
最后,我们看一下ReactDefaultBatchingStrategy
提供的公共API,可以从外界调用它
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()
的基础方法。
function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) { // scr: ----------> {a}
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// scr: -----------------------------------------------------> {b}
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
,这会触发分支{a},该分支通向ReactDefaultBatchingStrategy.batchedUpdates()
;。
2)batchedUpdates()
将ReactDefaultBatchingStrategy.isBatchingUpdates
设置为true
,并初始化transaction
;
3)batchedUpdates
的callback
参数是enqueueUpdate()
本身,因此enqueueUpdate
将再次使用transaction.perform()
再次输入。 请注意,两个包装器的前置方法(initialize()
)都是emptyFunction
,因此两次调用enqueueUpdate()
之间什么都没有发生。
4)当第二次输入enqueueUpdate()
时(在刚刚初始化的Transaction
上下文中),执行分支{b};
...
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)最后,调用RESET_BATCHED_UPDATES
的后方法(close()
),这会将ReactDefaultBatchingStrategy.isBatchingUpdates
设置为false
并完成循环。
重要的是要注意,应该在ReactDefaultBatchingStrategy.isBatchingUpdates:false
的上下文中执行3)和6)之间对enqueueUpdate()
的任何后续调用,这意味着在这种情况下将使用分支{b}。 所以就像
->dirtyComponents.push(component);
->dirtyComponents.push(component);
->dirtyComponents.push(component);
...
----->ReactUpdates.flushBatchedUpdates
Wrap-up
(原文链接Understanding The React Source Code - UI Updating (Transaction) VI)
网友评论