react源码阅读笔记(3)batchedUpdates与Transaction

本文使用的是react 15.6.1的代码

在上篇文章组件的渲染中_renderNewRootComponent中最后调用了ReactUpdates.batchedUpdates函数,上文中只是简单的分析了最后实际是在内部调用了其中的第一个参数batchedMountComponentIntoNode,其实并没有那么简单,今天我们就来看看batchedUpdates实际做了些什么内容

ReactUpdates.batchedUpdates

//批量更新方法
function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected(); //不用理会,单纯的打印方法
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

实际上是调用了batchingStrategy.batchedUpdates中的方法,但是翻遍了整个文件,发现batchingStrategy这个对象一直是null,那么batchingStrategy是怎么初始化的呢?在ReactUpdates中,发现定义了这样一个对象ReactUpdatesInjection,其中代码如下

var ReactUpdatesInjection = {
  injectReconcileTransaction: function(ReconcileTransaction) {
    ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
  },

  injectBatchingStrategy: function(_batchingStrategy) {
    batchingStrategy = _batchingStrategy;
  },
};

其中有一个方法injectBatchingStrategy,那么是不是外部谁调用了该方法,使得最后batchingStrategy被注入进来了,在ReactDOM源码中,我们发现有一行

ReactDefaultInjection.inject(); //注入事件,环境变量,各种参数值

查看ReactDefaultInjection.inject()方法后,又找到了这样一行代码

ReactInjection.Updates.injectBatchingStrategy(ReactDefaultBatchingStrategy);

再次查看ReactInjection中的源代码

var ReactInjection = {
  Component: ReactComponentEnvironment.injection,
  DOMProperty: DOMProperty.injection,
  EmptyComponent: ReactEmptyComponent.injection,
  EventPluginHub: EventPluginHub.injection,
  EventPluginUtils: EventPluginUtils.injection,
  EventEmitter: ReactBrowserEventEmitter.injection,
  HostComponent: ReactHostComponent.injection,
  Updates: ReactUpdates.injection,
};

也就是说,在ReactDOM之中,会执行到 ReactUpdates.injection.injectBatchingStrategy方法,同时,将ReactDefaultBatchingStrategy传入其中,所以最后batchingStrategy = ReactDefaultBatchingStrategy,那么回到前文batchingStrategy.batchedUpdates(callback, a, b, c, d, e),这里面的batchedUpdates实际就是执行的ReactDefaultBatchingStrategy中的方法
源码地址[ReactDefaultBatchingStrategy]

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  /**
   * Call the provided function in a context within which calls to `setState`
   * and friends are batched such that components aren't updated unnecessarily.
   */
  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) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);//重点代码
    }
  },
};

代码很简单,第一次调用的时候ReactDefaultBatchingStrategy.isBatchingUpdates肯定是false,那么就会调用transaction.perform方法,并将相应回调传入其中,那么我们来看看ReactDefaultBatchingStrategy相关代码


var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
  this.reinitializeTransaction();
}

// ReactDefaultBatchingStrategyTransaction.prototype添加Transaction对象的方法,如reinitializeTransaction,同时用新的函数覆盖getTransactionWrappers
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },
});

var transaction = new ReactDefaultBatchingStrategyTransaction();

发现transation是ReactDefaultBatchingStrategyTransaction的实例,但是perform方法是Transaction.js提供的,我们看看Transaction方法


'use strict';

var invariant = require('invariant');

var OBSERVED_ERROR = {};

/**
 * `Transaction` creates a black box that is able to wrap any method such that
 * certain invariants are maintained before and after the method is invoked
 * (Even if an exception is thrown while invoking the wrapped method). Whoever
 * instantiates a transaction can provide enforcers of the invariants at
 * creation time. The `Transaction` class itself will supply one additional
 * automatic invariant for you - the invariant that any transaction instance
 * should not be run while it is already being run. You would typically create a
 * single instance of a `Transaction` for reuse multiple times, that potentially
 * is used to wrap several different methods. Wrappers are extremely simple -
 * they only require implementing two methods.
 *
 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>
 *
 * Use cases:
 * - Preserving the input selection ranges before/after reconciliation.
 *   Restoring selection even in the event of an unexpected error.
 * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
 *   while guaranteeing that afterwards, the event system is reactivated.
 * - Flushing a queue of collected DOM mutations to the main UI thread after a
 *   reconciliation takes place in a worker thread.
 * - Invoking any collected `componentDidUpdate` callbacks after rendering new
 *   content.
 * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
 *   to preserve the `scrollTop` (an automatic scroll aware DOM).
 * - (Future use case): Layout calculations before and after DOM updates.
 *
 * Transactional plugin API:
 * - A module that has an `initialize` method that returns any precomputation.
 * - and a `close` method that accepts the precomputation. `close` is invoked
 *   when the wrapped process is completed, or has failed.
 *
 * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
 * that implement `initialize` and `close`.
 * @return {Transaction} Single transaction for reuse in thread.
 *
 * @class Transaction
 */
var TransactionImpl = {
  reinitializeTransaction: function(): void {
    //获取wrappers
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },

  _isInTransaction: false,

  /**
   * @abstract
   * @return {Array<TransactionWrapper>} Array of transaction wrappers.
   * 由具体Transaction来提供,如ReactDefaultBatchingStrategyTransaction
   */
  getTransactionWrappers: null,

  isInTransaction: function(): boolean {
    return !!this._isInTransaction;
  },

  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 {
    var errorThrown;
    var ret;
    try {
      //正在Transaction ing中
      this._isInTransaction = true;
     
      errorThrown = true;

      this.initializeAll(0);
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        //如果initializeAll没有抛出异常的话
        if (errorThrown) {
          // If `method` throws, prefer to show that stack trace over any thrown
          // by invoking `closeAll`.
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          // Since `method` didn't throw, we don't want to silence the exception
          // here.
          //进入close生命周期
          this.closeAll(0);
        }
      } finally {
        // Transaction结束
        this._isInTransaction = false;
      }
    }
    return ret;
  },

  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;
        //调用wrapper的initialize方法
        this.wrapperInitData[i] = wrapper.initialize
          ? wrapper.initialize.call(this)
          : null;
      } finally {
        //如果调用initialize有问题,则startIndex+1在调用
        if (this.wrapperInitData[i] === OBSERVED_ERROR) {
        
          try {
            this.initializeAll(i + 1);
          } catch (err) {}
        }
      }
    }
  },

 
  closeAll: function(startIndex: number): void {
    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;

module.exports = TransactionImpl;

代码比较少,从代码注释的简图看,在创建Transations的时候,会先注入wrapper,当调用perform方法的时候,会依次调用wrapper的initialize方法,随后在调用perform中传入的方法,最后在调用依次调用wrapper中的close方法,简单看下来,有设计模式(代理)的思路在里面,我们具体看perform的实现首先,调用了initializeAll方法,然后循环拿到transactionWrappers依次调用initialize方法,随后调用perform的第一参数,最后又调用closeAll方法,依次调用transactionWrappers中的close的方法,最后将_isInTransaction置为false表示transation结束。和图中描述的一样,也就是说,不同的Transation会去实现不同的wrapper来代理具体的方法,那么ReactDefaultBatchingStrategyTransaction的wrapper是什么?在ReactDefaultBatchingStrategy之中,实例化ReactDefaultBatchingStrategyTransaction时会调用
this.reinitializeTransaction()方法,该方法中调用了getTransactionWrappers获取具体的wrappers,对应的,ReactDefaultBatchingStrategyTransaction重写了getTransactionWrappers方法,因此,得到返回值
[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]
这是wrapper的实现

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

代码比较简单,两个wraper都只有close方法,根据代码描述,会先执行FLUSH_BATCHED_UPDATES的close方法也就是ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)

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);
    }
  }
};

react运行中当存在脏组件的时候,会用transaction调用runBatchedUpdates方法,在执行之前,react使用了对象池这样的优化手段来获取/生成transaction对象,该部分暂时跳过,后面在细读,我们在来看看runBatchedUpdates做了些什么

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;

  // 排序,保证 dirtyComponent 从父级到子级的 render 顺序
  dirtyComponents.sort(mountOrderComparator);

  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];

    // 获取该组件的回调数组
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      // Duck type TopLevelWrapper. This is probably always true.
      if (component._currentElement.type.isReactTopLevelWrapper) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }
    // 更新该 dirtyComponent 重点代码
    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction,
      updateBatchNumber,
    );

    if (markerName) {
      console.timeEnd(markerName);
    }

    // 如果存在组件回调函数,将其放入回调队列中
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance(),
        );
      }
    }
  }
}

代码不多,首先是将脏components进行排序,变成从父组件到子组件的顺序排列,随后遍历组件调用performUpdateIfNecessary更新数据,同时将组件的回调压入回调队列中,等待最后调用,这里我们注意到并不是更新一个组件的时候就调用其回调,而是等到所有组件都更新完以后才去调用,看看核心代码performUpdateIfNecessary方法,内部就一行核心代码internalInstance.performUpdateIfNecessary(transaction)即调用了ReactComponent对象的performUpdateIfNecessary方法,即ReactCompositeComponent.performUpdateIfNecessary方法

performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(
        this,
        this._pendingElement,
        transaction,
        this._context,
      );
    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      //注意,调用了this.updateComponent方法,但是element以及content都是component自己的即_currentElement,_context
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context,
      );
    } else {
      this._updateBatchNumber = null;
    }
  }

绕了一圈,最后还是调用组件自己的updateComponent方法,而updateComponent会去执行React组件的生命周期,如componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render, componentDidUpdate。完成整套流程。后面我们慢慢介绍React的生命周期。

回到上面代码,刚才知道runBatchedUpdates是有事务去调用的,react事务会先执行wrapper中的init方法,在执行close方法,我们在来看看ReactUpdatesFlushTransaction的wrapper相关代码做了什么。


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];

看到上面代码,不得不感叹react设计的巧妙,这里巧妙的使用了transition的生命周期,在initialize时获取当前dirtyComponent的个数,最后结束(close)的时候再次校验,如果数量不对,那么说明dirtyComponents有新增,再一次进行flushBatchedUpdates,因为更新操作是批量的,因此不会出现改一个就执行一次的情况,大大提高了性能,第二个wrapper主要是处理回调队列里面的内容,当更新处理完成后,最后调用UPDATE_QUEUEING close方法,调用notifyAll去执行所有dirtyComponent的回调函数队列

总结

注入的设计极大的方便了代码的维护,将实现给原子化,若以后版本升级,算法更新,可以直接切换injectiion中的原子换成新的版本即可。同时,高度抽象的代码也给阅读带来了困难

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容