Photo by Jacob Ufkes on Unsplash
本文也同时发表在我的博客和 HACKERNOON
从某种程度来说, 优雅, 高效的界面更新是 React 的核心竞争力在深入理解 React 里用来加速界面更新的各种奇技淫巧 (比如 virtual DOM 和 Diffing 算法) 之前, 我们需要先了解上述技巧的打开方式, Transaction
本篇涉及的文件:
renderers/shared/utils/Transaction.js: Transaction 核心类定义在这里
renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js: ReactDefaultBatchingStrategyTransaction 和它的 API 包装类 ReactDefaultBatchingStrategy 定义在这里
renderers/shared/stack/reconciler/ReactUpdates.js: 这里的 enqueueUpdate() 作为主要路径用到了 ReactDefaultBatchingStrategy
之前的文章一般是从常用 API 入手, 而本篇则会用自底向上的方式来解读这段逻辑
那我们先来看看
Transaction 这个核心类
这个类的实际 public 函数是 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
除了调用 callback (这个函数的第一个参数) 以外, perform() 还做了两件事 1) 在调 callback 之前调用 initializeAll() 和 2) 之后调用 closeAll()
这里 errorThrown 用来标记调用 method.call() 的时候是否发生异常 (如果抛异常了 errorThrown 不会被设置回 false, 并且逻辑会直接走到 finally)
下面我们看一下上述的前置和后置函数,
...
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.e., 下标从 i + 1 到 transactionWrappers.length-1) 的 initialize() 函数然后异常会打断 for 循环和整个 initializeAll() 函数调用, 而直接走到 perform() 的 finally 中这样, 就在初始化有异常的情况下绕开了
ret = method.call(scope, a, b, c, d, e, f);
调用最后 closeAll() 会在同一个 finally 代码块中关闭事物流
现在我们明白了 Transaction 的核心是啥, 但是具体用来做什么呢? 下一节我们用一个 Transaction 的实例来回答这个问题
ReactDefaultBatchingStrategyTransaction
首先, ReactDefaultBatchingStrategyTransaction 是一个 Transaction 的子类, 并且实现了 getTransactionWrappers() :
...
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
});
...
ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
其次, TRANSACTION_WRAPPERS 就是 this.transactionWrappers 的实体了正是它们提供了 perform() 用到的前置函数 (initialize()) 和后置函数 (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() , 这些函数会被分别用在 Transaction.initializeAll(), 和 Transaction.closeAll() 中
3) 将 ReactDefaultBatchingStrategyTransaction 定义成一个单例
最后我们看一下 ReactDefaultBatchingStrategy 里面的 public 函数
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 是通过注入机制 {第二篇 *5} 赋值给 ReactUpdates 的 batchingStrategy 的然后 ReactDefaultBatchingStrategy.batchedUpdates() 则被用在 ReactUpdates.enqueueUpdate() 里这个 函数也是 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 再次调用 注意这里两个 wrapper 的前置函数 (initialize()) 都是 emptyFunction 所以两次调用 enqueueUpdate() 之间并不会发生任何事情;
4) 当第二次进入 enqueueUpdate() (在刚刚启动的 transaction 上下文中),{b}分支会被触发
...
dirtyComponents.push(component);
...
5)enqueueUpdate() 返回后, FLUSH_BATCHED_UPDATES 的后置函数 (close()) 会被调用这些后置函数会处理上一步中标记为 dirtyComponents 的组件
*8 我们会在下篇讨论这个 FLUSH_BATCHED_UPDATES.close() 和 ReactUpdates.flushBatchedUpdates() 函数体
6)RESET_BATCHED_UPDATES 的后置函数会被调用, 然后将 ReactDefaultBatchingStrategy.isBatchingUpdates 设置成 false 至此整个流程完成
这里很重要的一点是, 在 3) 到 6) 之间的后续 enqueueUpdate() 调用都会在 ReactDefaultBatchingStrategy.isBatchingUpdates:false 的上下文中执行, 这意味着这些调用都会走{b}分支
->dirtyComponents.push(component);
->dirtyComponents.push(component);
->dirtyComponents.push(component);
...
----->ReactUpdates.flushBatchedUpdates
简单总结
今天先写到这如果您觉得这篇不错, 可以点赞或关注这个专栏
来源: https://zhuanlan.zhihu.com/p/33350567