React的事务机制

3,805 阅读4分钟

概念

事务是一个繁琐的概念,在React中事务的核心作用是保证数据的一致性,以及出错时候的回滚。在React中事务主要做的事情是包装。

Transaction 类

先来看看基础类Transaction类。简单的来说,Transaction类的主要作用使用提供的包装纸(Wrappe)来包装一个函数。 React给的一个图能说明Transaction:

 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>

包装纸(Wrapper)

包装纸是提供给事务使用的一个对象,其内包含两个方法。一个为初始化方法 initialize,一个是执行结束的方法close。 Transaction类会包装一个方法 func。包装的过程就是将提供的包装纸里的initialize方法放在func之前,close方法都放在func之后。这样func执行的时候,前后就植入了一些代码做一些初始化或者是清理的工作。

一个Wrapper比如:

    <!--
        ReactReconcileTransaction类的一个Wrapper
    -->
    var ON_DOM_READY_QUEUEING = {
    
      initialize: function () {
        this.reactMountReady.reset();
      },
        
      close: function () {
        this.reactMountReady.notifyAll();
      }
    };

Transaction究竟做了什么

简单的来说,Transaction会接受一个方法 func,和一组Wrapper。Transaction会在func执行之前,执行一组Wrapper中的initialize方法。而后执行func方法,在func方法执行完了之后,执行Wrapper提供的所有close方法。

看一下核心方法:perfom。

    perform: function (method, scope, a, b, c, d, e, f) {
    var errorThrown;
    var ret;
    try {
        <!--
            表示当前是否正在进行事务操作
        -->
        this._isInTransaction = true;
        errorThrown = true;
        <!--
            method调用之前先调用initializeAll方法
            initializeAll方法是一个合成方法,主要是合成了一组Wrapper中的所有initialize方法。
        -->
        this.initializeAll(0);
        <!--
            执行了提供的方法
        -->
        ret = method.call(scope, a, b, c, d, e, f);
        errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
        <!--
            如果出错了就执行Wrapper提供的所有close方法,而closeAll是将一组Wrapper的close方法取出来放一起执行。
        -->
          try {
            this.closeAll(0);
          } catch (err) {}
        } else {
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },

举个🌰

我们在看React的事件注册机制的时候,使用事务将注册的侦听器存储了起来。也就是在ReactDOMComponent模块的enqueuePutListener方法中。

    transaction.getReactMountReady().enqueue(putListener, {
        id: id,
        registrationName: registrationName,
        listener: listener
    });

首先说一下这里使用的事务是ReactReconcileTransaction类的实例。

看一下 ReactReconcileTransaction 类。

    <!--
        定义了一组Wrapper。
    -->
    <!--
        SELECTION_RESTORATION的作用是保证当前选择的文本或者是输入不会因为事务的执行而受影响
    -->
    var SELECTION_RESTORATION = {
       
          initialize: ReactInputSelection.getSelectionInformation,
          
          close: ReactInputSelection.restoreSelection
    };
    <!--
        抑制无意识的调度事件,比如focus或者是blur事件
    -->
    var EVENT_SUPPRESSION = {
     
      initialize: function () {
        var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
        ReactBrowserEventEmitter.setEnabled(false);
        return currentlyEnabled;
      },
     
      close: function (previouslyEnabled) {
        ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
      }
    };
    
    <!--
        这个Wrapper的作用,在组件挂在完成或者是更新完成之后调用回调函数。
    -->
    var ON_DOM_READY_QUEUEING = {
     
      initialize: function () {
        this.reactMountReady.reset();
      },
    
      close: function () {
        this.reactMountReady.notifyAll();
      }
    };

解释一下方法,这里会用到了一个池化的例子 Callbackqueue类的池化传送

     transaction.getReactMountReady().enqueue(putListener, {
        id: id,
        registrationName: registrationName,
        listener: listener
    });
    
    <!--
         transaction.getReactMountReady()会返回一个 Callbackqueue 对象池化的一个实例。
         
         Callbackqueue对象有两个属性:
         _callbacks 数组:用来存储回调函数
         _contexts 数组:用来存储回调函数用到的参数
         
        这个实例提供了两个核心方法
        
         enqueue 方法:作用是入队(push进_callbacks)一个回调函数
         notifyAll:方法则是执行入队的回调函数。
         
         enqueue方法只是将 putListener方法放在_callbacks中,
        而后将对象 {id: id,registrationName: registrationName,listener: listener}
        放在 _contexts 中,表面上没有执行任何操作,那么React是怎么将侦听器放入listenerBank中呢?
        核心还是Transaction。
        
        我们之前说过,事务就是在被包装的方法被执行之前会插入一些方法执行,而后方法执行完毕之后会执行一些方法来做收尾工作。
        做收尾工作的是Wrapper中的 close方法。
        也就是 ON_DOM_READY_QUEUEING 这个Wrapper的close方法。
        this.reactMountReady.notifyAll();
    -->
    

看一下notifyAll方法

    <!--
        方法的主要作用就是将放入队列的函数拿出来执行一下,使用对应的参数。
    -->
    notifyAll: function () {
        var callbacks = this._callbacks;
        var contexts = this._contexts;
        if (callbacks) {
          this._callbacks = null;
          this._contexts = null;
          for (var i = 0; i < callbacks.length; i++) {
            callbacks[i].call(contexts[i]);
          }
          callbacks.length = 0;
          contexts.length = 0;
        }
    },

最后说一下 ReactReconcileTransaction类初始化的方法是React组件的挂载和更新。该事务类的主要作用是React的生命周期事务类。每一个事务都要包装一个方法,而ReactReconcileTransaction类包装的方法就是React的生命周期方法,主要是更新和挂载方法。