Photo by Felix Mooneeram on Unsplash
本文也同时发表在我的博客和 HACKERNOON
上次我们聊完了Transaction
核心类和它的一个实例ReactDefaultBatchingStrategyTransaction
。然而这个Transaction
实例仅仅是一个开始 。
本篇中,我们将探究其它的Transaction
实例。看懂了这些,界面更新逻辑的轮廓就画的出来了。
本文用到的文件:
renderers/shared/stack/reconciler/ReactUpdates.js: 定义了本文的入口函数,flushBatchedUpdates()
;它也定义了ReactUpdatesFlushTransaction
,本文会重点关注的其中一个Transaction
实例
shared/utils/PooledClass.js: 定义了PooledClass
,这个类被用于开启其它类的实例池功能
renderers/dom/client/ReactReconcileTransaction.js: 定义了ReactReconcileTransaction
,另外一个重点关注的Transaction
我们从上次讨论到的函数 ReactUpdates.flushBatchedUpdates()
开始
...
var flushBatchedUpdates = function () {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) { // scr: not applied
...
}
}
};
...
ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js
之前提到过,这个函数会启动 ReactUpdatesFlushTransaction
来处理所有的dirtyComponents
,而这个即将启动的Transaction
链会最终调用runBatchedUpdates
来完成本文将会讨论的整个操作。
`PooledClass` - 实例池
上面一段代码里面有两个函数不太常见,getPooled()
和release()
。这两个函数是从PooledClass
继承而来,用于实现实例池:
a)如果从来没有分配过(ReactUpdatesFlushTransaction
)实例,getPooled()
创建一个新实例;
b)如果池里已经有实例了,getPooled()
直接从池里返回已经分配好的实例;
c)release()
并不是真的释放掉内存,而是将实例放回池子里。
和其他的各种“池”一样,这个实例池的目的也是减少不必要的资源(这里是内存)创建,和回收开销。
回到我们刚刚的例子:
...
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
...
ReactUpdatesFlushTransaction
实例只会在第一次进入while
循环时被创建。接下来的访问会直接从池里捞。
关于PooledClass
知道这些就完全不影响接下来的理解了,所以想跳过此小节的同学请ctrl-f “ReactUpdatesFlushTransaction”。你随意,我继续写
我们来看它的具体实现:
var PooledClass = {
addPoolingTo: addPoolingTo, // scr: ------> 1)
oneArgumentPooler: (oneArgumentPooler: Pooler), // scr: ------> 2)
...// scr: not used
};
module.exports = PooledClass;
PooledClass@shared/utils/PooledClass.js
1)addPoolingTo()
是(事实上的)“public”函数,它用来给既存的类注入实例池的功能;
2)oneArgumentPooler()
则是getPooled()
的底层实现。
下面我们看下addPoolingTo()
的函数体:
...
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;
...
var addPoolingTo = function<T>(
CopyConstructor: Class<T>,
pooler: Pooler,
): Class<T> & {
getPooled(): /* arguments of the constructor */ T,
release(): void,
} {
var NewKlass = (CopyConstructor: any);
NewKlass.instancePool = []; // scr: -------------> 1)
NewKlass.getPooled = pooler || DEFAULT_POOLER; // scr: -------> 2)
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE; // scr: -------------> 3)
}
NewKlass.release = standardReleaser; // scr: -------------> 4)
return NewKlass;
};
...
addPoolingTo@shared/utils/PooledClass.js
1)instancePool
就是上面说的实例池;
2) 将getPooled()
设置成 DEFAULT_POOLER
(a.k.a., oneArgumentPooler
);
3)将poolSize
赋值为10
;
4)将release()
设置成standardReleaser()
。
然后是getPooled()
和release()
的实现:
var oneArgumentPooler = function(copyFieldsFrom) {
var Klass = this;
if (Klass.instancePool.length) { // scr: -----------> 1)
var instance = Klass.instancePool.pop(); // scr: -----------> 1)
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
return new Klass(copyFieldsFrom); // scr: ----------------> 2)
}
};
oneArgumentPooler@shared/utils/PooledClass.js
...
var standardReleaser = function(instance) {
var Klass = this;
...
instance.destructor();
if (Klass.instancePool.length < Klass.poolSize) { // scr: ----> 3)
Klass.instancePool.push(instance); // scr: ----> 3)
}
};
standardReleaser@shared/utils/PooledClass.js
1)对应 b),这里面
Klass.call(instance, copyFieldsFrom);
使用copyFieldsFrom
作为参数调用了Klass
的构造函数;
2)对应a);
3)对应c)。
最后,我们看一下addPoolingTo()
是如何被外面调用的:
...
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
...
ReactUpdatesFlushTransaction@renderers/shared/stack/reconciler/ReactUpdates.js
ReactUpdatesFlushTransaction
...
function ReactUpdatesFlushTransaction() {
this.reinitializeTransaction();
this.dirtyComponentsLength = null;
this.callbackQueue = CallbackQueue.getPooled();
this.reconcileTransaction =
ReactUpdates.ReactReconcileTransaction.getPooled( // scr: ---> 2)
/* useCreateElement */ true,
);
}
// scr: --------------------------------------------------------> 1)
Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS; // scr: -----------------------> 3)
},
destructor: function() {
this.dirtyComponentsLength = null;
CallbackQueue.release(this.callbackQueue);
this.callbackQueue = null;
ReactUpdates.ReactReconcileTransaction.release( // scr: ----> 2)
this.reconcileTransaction
);
this.reconcileTransaction = null;
},
perform: function(method, scope, a) {
return Transaction.perform.call(
this,
this.reconcileTransaction.perform, // scr: ---------------> 2)
this.reconcileTransaction,
method,
scope,
a,
);
},
});
...
ReactUpdatesFlushTransaction@renderers/shared/stack/reconciler/ReactUpdates.js
1)这是另外一个Transaction
的实例,并且覆写了perform()
方法;
2)这个类并没有直接调用传给它的callback参数 (ReactUpdate.runBatchedUpdates
),而是在这个覆写过的ReactUpdatesFlushTransaction.perform()
中嵌套调用 另一个Transaction
(ReactReconcileTransaction
) 的perform()
函数,再把这个callback参数(i.e.,
ReactUpdate.runBatchedUpdates()
)透传下去。这里注意ReactReconcileTransaction
也开启了实例池;
3)TRANSACTION_WRAPPERS
定义了它的前置和后置函数:
...
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
if (this.dirtyComponentsLength !== dirtyComponents.length) {
dirtyComponents.splice(0, this.dirtyComponentsLength);
flushBatchedUpdates(); // scr: ----------------------> a)
} else {
dirtyComponents.length = 0; // scr: ----------------------> b)
}
},
};
var UPDATE_QUEUEING = { // scr: ------> we omit this wrapper for now
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
},
};
var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
...
ReactUpdatesFlushTransaction@renderers/shared/stack/reconciler/ReactUpdates.js
这里NESTED_UPDATES
的initialize()
1.5)缓存了dirtyComponents
的数量;然后在close()
中3)检查这个数量有没有变。如果变了{a}flushBatchedUpdates()
会被调用然后重新递归整个过程;或者{b}没变的话,它会将dirtyComponents.length
设置为0
,然后返回至上层Transaction
,ReactDefaultBatchingStrategyTransaction
{上一篇}的上下文。
对于(UPDATE_QUEUEING
里的)CallbackQueue
相关的操作我就不展开讨论了。留给以后的讨论组件生命周期文章再说。*9
简单总结下:

ReactReconcileTransaction
这仅仅是另一个普通的Transaction
:
var Mixin = {
/**
* @see Transaction
* @abstract
* @final
* @return {array<object>} List of operation wrap procedures.
* TODO: convert to array<TransactionWrapper>
*/
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS; // scr: -----------------------> 1)
},
// scr: ---------------------> we omit all CallbackQueue s for now
getReactMountReady: function() {
return this.reactMountReady;
},
// scr: ---------------------> we omit all CallbackQueue s for now
getUpdateQueue: function() {
return ReactUpdateQueue;
},
checkpoint: function() { // scr: -----------------------> not used
// reactMountReady is the our only stateful wrapper
return this.reactMountReady.checkpoint();
},
rollback: function(checkpoint) { // scr: ---------------> not used
this.reactMountReady.rollback(checkpoint);
},
// scr: ------------------------------------> for instance pooling
destructor: function() {
CallbackQueue.release(this.reactMountReady);
this.reactMountReady = null;
},
};
Object.assign(ReactReconcileTransaction.prototype, Transaction, Mixin);
// scr: --------------------------------------------------------> 2)
PooledClass.addPoolingTo(ReactReconcileTransaction);
module.exports = ReactReconcileTransaction;
ReactReconcileTransaction@renderers/dom/client/ReactReconcileTransaction.js
1)它的包装类定义在TRANSACTION_WRAPPERS
中;
2)之前提到过,它有实例池。
下面我们一把看完它的三个包装类:
/**
* Ensures that, when possible, the selection range (currently selected text
* input) is not disturbed by performing the transaction.
*/
var SELECTION_RESTORATION = {
/**
* @return {Selection} Selection information.
*/
initialize: ReactInputSelection.getSelectionInformation,
/**
* @param {Selection} sel Selection information returned from `initialize`.
*/
close: ReactInputSelection.restoreSelection,
};
/**
* Suppresses events (blur/focus) that could be inadvertently dispatched due to
* high level DOM manipulations (like temporarily removing a text input from the
* DOM).
*/
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);
},
};
/**
* Provides a queue for collecting `componentDidMount` and
* `componentDidUpdate` callbacks during the transaction.
*/
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();
},
};
var TRANSACTION_WRAPPERS = [
SELECTION_RESTORATION,
EVENT_SUPPRESSION,
ON_DOM_READY_QUEUEING,
];
ReactReconcileTransaction@renderers/dom/client/ReactReconcileTransaction.js
其实注释已经写的很清楚了:
SELECTION_RESTORATION
用来在界面更新操作之前存储文本框的选择状态(initialize()
),更新操作完了就恢复回来(close()
);
EVENT_SUPPRESSION
用来在界面操作的时候静默(浏览器)事件,并将之前的“事件是否关闭的状态”缓存(initialize()
),就是说不管之前有没有关,这里会把它暂时关闭;然后在更新操作结束后恢复之前缓存的状态。
(ON_DOM_READY_QUEUEING
里的)CallbackQueue
也不讨论了,留给以后的生命周期。*10
这里要注意,ReactReconcileTransaction
是依赖于原始的Transaction.perform()
,其对应的回调就是从本文开始被透传到这一步的ReactUpdate.runBatchedUpdates
。
可以用ctrl-f “runBatchedUpdates”查看一下它的透传轨迹。
这个ReactUpdate.runBatchedUpdates
会引出下一篇的内容。
简单总结下:

总结

今天先写到这。如果您觉得这篇不错,可以点赞或关注这个专栏。
感谢阅读!👋