美文网首页
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