本文使用的是react 15.6.1的代码
上篇介绍batchedUpdates时,最后flushBatchedUpdates方法中使用了Pool和CallbackQueus中的方法,这次我们就详细介绍一下这两个js。
Pool
承接上文,先来看看当时是怎么使用的。
var flushBatchedUpdates = function() {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
// 从缓存池中获取ReactUpdatesFlushTransaction对象
var transaction = ReactUpdatesFlushTransaction.getPooled();
// 调用runBatchedUpdates
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
在这里,首先发现,这里的transaction和 前文ReactDefaultBatchingStrategyTransaction不同,前文是通过new 的方式实例化的对象,而这里是调用ReactUpdatesFlushTransaction.getPooled()获取,朔本清源,看看ReactUpdatesFlushTransaction和ReactDefaultBatchingStrategyTransaction具体有什么区别
var NESTED_UPDATES = {
initialize: function() {
this.dirtyComponentsLength = dirtyComponents.length;
},
close: function() {
// 在批量更新,如果有新的dirtyComponents被push,那么,需要再一次批量更新,从新加入的dirtyComponents开始
if (this.dirtyComponentsLength !== dirtyComponents.length) {
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];
// ReactUpdatesFlushTransaction构造函数
function ReactUpdatesFlushTransaction() {
// 调用reinitializeTransaction
this.reinitializeTransaction();
this.dirtyComponentsLength = null;
//获取callbackQueue,reconcileTransaction实例;
this.callbackQueue = CallbackQueue.getPooled();
this.reconcileTransaction = ReactUpdates.ReactReconcileTransaction.getPooled(
/* useCreateElement */ true,
);
}
// 继承,覆盖相关方法
Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {
// 覆盖getTransactionWrappers方法
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
// 覆盖destructor方法,该方法会在放回缓存池中调用
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);
和普通的transation一样,似乎并没有什么不同,都有对应的wrappers getTransactionWrappers,对应构造函数,以及都重写了getTransactionWrappers方法。但是细细发现后,发现这个transaction重写了destructor方法,同时执行了命令PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);,那么,做这些是有什么作用呢?这里就引出了React的一个类库PooledClass,来看看他的实现吧!
var oneArgumentPooler = function(copyFieldsFrom) {
var Klass = this;
// 如果缓存池长度不为0,即存在实体对象,
if (Klass.instancePool.length) {
//那么直接从缓存池返回对应的对象
var instance = Klass.instancePool.pop();
// 同时调用一次该类构造函数,对该instance进行初始化
Klass.call(instance, copyFieldsFrom);
return instance;
} else {
// 如果没有缓存,则new一个出来
return new Klass(copyFieldsFrom);
}
}
var standardReleaser = function(instance) {
var Klass = this;
// 调用实例的destructor方法
instance.destructor();
// 将实例压入池子
if (Klass.instancePool.length < Klass.poolSize) {
Klass.instancePool.push(instance);
}
};
var DEFAULT_POOL_SIZE = 10;
var DEFAULT_POOLER = oneArgumentPooler;
type Pooler = any;
//重点代码
var addPoolingTo = function<T>(
CopyConstructor: Class<T>,
pooler: Pooler,
): Class<T> & {
getPooled(): /* arguments of the constructor */ T,
release(): void,
} {
// Casting as any so that flow ignores the actual implementation and trusts
// it to match the type we declared
var NewKlass = (CopyConstructor: any);
// 该类的具体实例缓存池
NewKlass.instancePool = [];
NewKlass.getPooled = pooler || DEFAULT_POOLER;
if (!NewKlass.poolSize) {
NewKlass.poolSize = DEFAULT_POOL_SIZE;
}
NewKlass.release = standardReleaser;
return NewKlass;
};
var PooledClass = {
addPoolingTo: addPoolingTo,
oneArgumentPooler: (oneArgumentPooler: Pooler),
twoArgumentPooler: (twoArgumentPooler: Pooler),
threeArgumentPooler: (threeArgumentPooler: Pooler),
fourArgumentPooler: (fourArgumentPooler: Pooler),
};
代码很简单,主要是提供了 addPoolingTo这个方法,在页面调用该方法后,会将getPooled以及release方法以及instancePool挂载到对应的类上,当调用getPooled方法后,会优先从instancePool去寻找是否有已经生成的实例,如果有,将其初始化(执行class的构造函数),没有的话,则new一个出来。当对象使用完毕后,调用一下release方法,这时,会调用实例的destructor方法去销毁实例中的对象等数据,同时放入缓存池中等待下一次调用,为什么react中要用这样的方法而不是直接new呢。因为new 一个function的时候,其会在原型链去查找属性(比较耗时),
详细见 继承与原型链
CallbackQueue
CallbackQueue代码比较简单,我们直接看看实现吧
class CallbackQueue<T> {
// 回调队列
_callbacks: ?Array<() => void>;
//上下文
_contexts: ?Array<T>;
_arg: ?mixed;
constructor(arg) {
this._callbacks = null;
this._contexts = null;
this._arg = arg;
}
/**
* Enqueues a callback to be invoked when `notifyAll` is invoked.
*
* @param {function} callback Invoked when `notifyAll` is invoked.
* @param {?object} context Context to call `callback` with.
* @internal
*/
enqueue(callback: () => void, context: T) {
//将回调和上下文塞入队列中
this._callbacks = this._callbacks || [];
this._callbacks.push(callback);
this._contexts = this._contexts || [];
this._contexts.push(context);
}
/**
* 执行队列中所有回调函数
*
* @internal
*/
notifyAll
() {
var callbacks = this._callbacks;
var contexts = this._contexts;
var arg = this._arg;
if (callbacks && contexts) {
this._callbacks = null;
this._contexts = null;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i].call(contexts[i], arg);
}
callbacks.length = 0;
contexts.length = 0;
}
}
/**
* 检查点,返回队列长度
* @returns {number}
*/
checkpoint() {
return this._callbacks ? this._callbacks.length : 0;
}
/**
*
* @param len
*/
rollback(len: number) {
if (this._callbacks && this._contexts) {
this._callbacks.length = len;
this._contexts.length = len;
}
}
/**
* 重置队列以及上下文
*
* @internal
*/
reset() {
this._callbacks = null;
this._contexts = null;
}
/**
* Pool调用release后会执行
*/
destructor() {
this.reset();
}
}
module.exports = PooledClass.addPoolingTo(CallbackQueue);
代码比较简单,用flow来编写。不过从设计思路上可以发现,在react中,往往是把上下文以及组件回调先存入这个统一的队列中,最后执行notifyAll来顺序执行回调。同时队列中提供了这样几个API供外部调用分别是
- enqueue : 将回调和上下文塞入队列的方法
- notifyAll: 通知调用回调队列的方法
- checkpoint: 检查点,返回队列长度
- rollback: 设置队列长度为指定数字
- reset: 重置队列以及上下文