React事务机制解析

版权声明:欢迎转载,转载请注明原始出处 https://blog.csdn.net/xiaomingelv/article/details/86183735

对react有一定了解的开发人员应该都听说过react的事务机制,这个机制几乎贯穿于react所有提供的方法,包括react中最常使用的setState函数

那么,react的事务机制到底是一种什么样的机制呢,为了解释事务机制的实现原理,react源码中用注释画出了这样一幅图

根据这幅图,我们可以这样理解事务,react中用事务执行方法,就是用wrapper(称之为套套吧)把方法包裹起来,然后每个wapper中都提供一个initialize方法和一个close方法,当需要使用事务调用一个方法,例如上图中的anyMethod时,使用事务提供的perform方法,将需要执行的方法传入,这个时候就会按顺序执行wrapper.initalize,anyMethod,wrapper.close,而且,事务还支持多个事务的嵌套,当执行方法被多个wapper包裹时,事务会先按顺序执行所有的initalize方法,再执行anyMethod,最后按顺序执行所有的close函数,例如上图就表示会按以下顺序执行wrapper1.initalize,wrapper2.initalize,anyMethod,wrapper1.close,wrapper2.close

那么事务在react中到底是怎么应用的呢?我们透过下面这段代码来看看如何使用事务

var ReactUpdates = require('ReactUpdates');
var Transaction = require('Transaction');

var emptyFunction = require('emptyFunction');

//第二个wrapper
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

//第一个wrapper
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

//wrapper列表
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

//事务构造函数
function ReactDefaultBatchingStrategyTransaction() {
//原型中定义的初始化方法
  this.reinitializeTransaction();
}

//继承原型
Object.assign(
  ReactDefaultBatchingStrategyTransaction.prototype,
  Transaction.Mixin,
  {
    getTransactionWrappers: function() {
      return TRANSACTION_WRAPPERS;
    },
  }
);

//新建一个事务
var transaction = new ReactDefaultBatchingStrategyTransaction();

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) {
      callback(a, b, c, d, e);
    } else {
      //在这个地方调用事务,callback是从外部传入的方法,此处无需关心callback,只需要知道它是一个待执行方法即可
      transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

代码不长,但确是react中用来控制渲染逻辑最关键的代码,此处只是为了展示事务的使用方法,我们无需关心代码做了些什么,我们可以看到上面的代码定义了一个叫ReactDefaultBatchingStrategyTransaction的构造函数,这个其实就是我们自定义的一个事务,并且在构造函数的原型上实现了一个叫getTransactionWrappers的接口,代码中的这个接口返回了一个数组,而这个数组的每一个项都包含了initalize和close方法,当调用perform方法时,事务会依次去调用这两个方法,而我们的看到的perform方法和构造函数中的reinitializeTransaction方法都是在Transaction.Mixin中定义的,这里面提供了实现事务所需要的所有基础功能,我们可以来看一下源代码的实现逻辑

'use strict';
var invariant = require('invariant');

var Mixin = {
  //事务初始化函数
  reinitializeTransaction: function() {
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },
  _isInTransaction: false,

  //抽象接口,此处的getTransactionWrappers方法将会被外部定义的getTransactionWrappers方法所覆盖
  getTransactionWrappers: null,

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

  perform: function(method, scope, a, b, c, d, e, f) {
    var errorThrown;
    var ret;
    try {
      this._isInTransaction = true;
      errorThrown = true;
      //执行所有的initalize方法
      this.initializeAll(0);
      //执行真正的方法
      ret = method.call(scope, a, b, c, d, e, f);
      errorThrown = false;
    } finally {
      try {
        if (errorThrown) {
          try {
            this.closeAll(0);
          } catch (err) {
          }
        } else {
          //正常状态下执行所有close方法
          this.closeAll(0);
        }
      } finally {
        this._isInTransaction = false;
      }
    }
    return ret;
  },

//执行所有wrapper中的initialize函数
  initializeAll: function(startIndex) {
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      try {
        //定义一个初始值,当finally里面匹配到this.wrapperInitData[i]未改变时,会忽略此项,从下一个项开始重新执行一次初始化,这么做的原因。。。官方解释为用catch使调试变的复杂,开心就好
        this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
        //执行initialize方法
        this.wrapperInitData[i] = wrapper.initialize ?
          wrapper.initialize.call(this) :
          null;
      } finally {
        if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
          try {
            this.initializeAll(i + 1);
          } catch (err) {
          }
        }
      }
    }
  },
//执行所有wrapper中的close函数
  closeAll: function(startIndex) {
    invariant(
      this.isInTransaction(),
      'Transaction.closeAll(): Cannot close transaction when none are open.'
    );
    var transactionWrappers = this.transactionWrappers;
    for (var i = startIndex; i < transactionWrappers.length; i++) {
      var wrapper = transactionWrappers[i];
      var initData = this.wrapperInitData[i];
      var errorThrown;
      try {
        //标记是否有执行异常,这么做的原因。。。官方解释为用catch使调试变的复杂,开心就好
        errorThrown = true;
        //执行close方法
        if (initData !== Transaction.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;
  },
};

var Transaction = {
  Mixin: Mixin,
  OBSERVED_ERROR: {},
};

module.exports = Transaction;

这就是整个事务机制的实现代码,没有删减,逻辑非常清晰,也很简单,这里其实就是使用了原型模式,这个就是所有事务的原型,前面我们看到在自定义事务里面使用的perform方法和reinitializeTransaction方法都可以在这里面找到,其实核心原理就是定义一个原型,里面有一个用来存放wrappers(称之为套套吧)的抽象接口,会由继承于这个原型的子类覆盖,由子类来定义真正要执行的方法前后执行的所有东西,我们可以看到perform方法的逻辑其实也很清晰,就是先调用initializeAll方法执行所有的initialize方法,然后执行传入的主体方法,之后执行closeAll来执行所有的close方法,所以,看起来,目测也不是什么高深的东西,也就是在执行方法之前强行添加入口跟出口方法,代码比较简单,都加了注释,感兴趣的话可以自行了解一下

这里面有个比较骚的操作在于,不管在initializeAll还是closeAll中,都是使用一个临时变量来标记当前的执行过程是否有异常,舍弃了用catch方法,改用在finally方法里面处理异常,官方的说法是捕获错误会使调试变的困难,具体原因应该是因为作为基础的原型类,此处不应该去捕获外部传入方法抛出的异常,应该让异常直接抛回外部,由外部函数自行处理,如果此处强行将异常捕获,确实会导致外部函数调试变的比较困难

没有更多推荐了,返回首页