美文网首页
react源码阅读笔记(4)Pool与CallbackQueue

react源码阅读笔记(4)Pool与CallbackQueue

作者: Kaku_fe | 来源:发表于2017-10-30 19:28 被阅读74次

本文使用的是react 15.6.1的代码

上篇介绍batchedUpdates时,最后flushBatchedUpdates方法中使用了PoolCallbackQueus中的方法,这次我们就详细介绍一下这两个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()获取,朔本清源,看看ReactUpdatesFlushTransactionReactDefaultBatchingStrategyTransaction具体有什么区别


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供外部调用分别是

  1. enqueue : 将回调和上下文塞入队列的方法
  2. notifyAll: 通知调用回调队列的方法
  3. checkpoint: 检查点,返回队列长度
  4. rollback: 设置队列长度为指定数字
  5. reset: 重置队列以及上下文

相关文章

网友评论

      本文标题:react源码阅读笔记(4)Pool与CallbackQueue

      本文链接:https://www.haomeiwen.com/subject/anclpxtx.html