美文网首页
【译】了解React源代码-UI更新(事务)6

【译】了解React源代码-UI更新(事务)6

作者: nextChallenger | 来源:发表于2019-10-15 17:35 被阅读0次

    【译】了解React源代码-初始渲染(简单组件)1
    【译】了解React源代码-初始渲染(简单组件)2
    【译】了解React源代码-初始渲染(简单组件)3
    【译】了解React源代码-初始渲染(类组件)4
    【译】了解React源代码-初始渲染(类组件)5
    【译】了解React源代码-UI更新(事务)6
    【译】了解React源代码-UI更新(事务)7
    【译】了解React源代码-UI更新(单个DOM)8
    【译】了解React源代码-UI更新(DOM树)9


    在某种程度上,复杂而有效的用户界面更新是使React React的原因。 但是,在我们深入研究能够实现UI更新的众所周知的机制(虚拟DOM和差异算法)之前,我们需要了解Transaction,它将控制权从高级API setState()转移到那些底层处理逻辑。

    本文中使用的文件:

    renderers / shared / utils / Transaction.js
    定义核心Transaction

    renderers / shared / stack / reconciler / ReactDefaultBatchingStrategy.js
    定义ReactDefaultBatchingStrategyTransaction及其API包装器ReactDefaultBatchingStrategy

    renderers / shared / stack / reconciler / ReactUpdates.js
    定义使用ReactDefaultBatchingStrategyenqueueUpdate()

    Unlike the previous posts that start from everyday APIs and move down the call stack. This post will take a bottom up approach.
    与以前的文章不同,这些文章从日常的API开始并向下移动到调用栈。 这篇文章将采取自下而上的方法。

    首先,我们来看一下

    事务核心类

    此类中唯一的事实上的“公共”方法是perform,它也提供其核心功能:

    ...
    /**
    ...
       *
       * @param {function} method Member of scope to call.
       * @param {Object} scope Scope to invoke from.
       * @param {Object?=} a Argument to pass to the method.
       * @param {Object?=} b Argument to pass to the method.
       * @param {Object?=} c Argument to pass to the method.
       * @param {Object?=} d Argument to pass to the method.
       * @param {Object?=} e Argument to pass to the method.
       * @param {Object?=} f Argument to pass to the method.
       *
       * @return {*} Return value from `method`.
       */
      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 {
        /* eslint-enable space-before-function-paren */
    ...
        var errorThrown;
        var ret;
        try {
          this._isInTransaction = true;
    ...
          // one of these calls threw.
          errorThrown = true;
          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 {
    ...
              this.closeAll(0);
            }
          } finally {
            this._isInTransaction = false;
          }
        }
        return ret;
      },
    ...
    
    TransactionImpl@renderers/shared/utils/Transaction.js
    

    除了调用作为第一个参数传递给它的回调方法外,perform()只是1)在回调之前调用initializeAll(),然后在2)之后调用closeAll()

    在这里,errorThrown用来指示method.call()中发生的异常,在这种情况下,逻辑直接跳转到finally块,然后将errorThrown设置为false

    接下来,我们来看一下perform()之前和之后调用的两个方法的实现:

    ...
      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;
            this.wrapperInitData[i] = wrapper.initialize
              ? wrapper.initialize.call(this)
              : null;
          } finally {
            if (this.wrapperInitData[i] === OBSERVED_ERROR) {
              try {
                this.initializeAll(i + 1);
              } catch (err) {}
            }
          }
        }
      },
    
    ...
      closeAll: function(startIndex: number): void {
    ...// scr: sanity check
        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;
    
    TransactionImpl@renderers/shared/utils/Transaction.js
    

    这两个方法只是简单地迭代this.transactionWrappers并分别调用其initialize()close()

    this.transactionWrappersTransaction的默认构造函数中使用this.getTransactionWrappers()初始化:

    ...
      reinitializeTransaction: function(): void {
        this.transactionWrappers = this.getTransactionWrappers();
        if (this.wrapperInitData) {
          this.wrapperInitData.length = 0;
        } else {
          this.wrapperInitData = [];
        }
        this._isInTransaction = false;
      },
    ...
    
    TransactionImpl@renderers/shared/utils/Transaction.js
    

    我们将很快看到this.transactionWrappers到底是什么。

    这里的异常处理细节有些有趣。 以initializeAll()作为实例。 如果initialize()中发生异常,则finally块(而不是catch)将处理其余this.transactionWrappers(即,从i +1transactionWrappers.length-1)的initialize()。 然后,异常会中断for循环和整个initializeAll()逻辑,并一直处理到perform()initializeAll()的调用者)内的finally块,从而有效地跳过了

    ret = method.call(scope, a, b, c, d, e, f);
    

    在异常初始化的情况下。 最后,在相同的finally块中调用closeAll()以完成事务。

    现在我们知道本质上什么是Transaction,但是它的作用是什么? 为了回答这个问题,我们以Transaction实例化为例,它是UI更新的事务入口点。

    ReactDefaultBatchingStrategyTransaction

    首先,ReactDefaultBatchingStrategyTransaction是实现getTransactionWrappers()Transaction的子类:

    ...
    Object.assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
      getTransactionWrappers: function() {
        return TRANSACTION_WRAPPERS;
      },
    });
    ...
    
    ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
    

    其次,TRANSACTION_WRAPPERSthis.transactionWrappers的来源,它为上一节中使用的perform()提供了pre(initialize())和post(close())函数。

    ...
    var RESET_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: function() {
        ReactDefaultBatchingStrategy.isBatchingUpdates = false;
      },
    }; // scr: -----------------------------------------------------> 2)
    
    var FLUSH_BATCHED_UPDATES = {
      initialize: emptyFunction,
      close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
    }; // scr: -----------------------------------------------------> 2)
    
    var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];  // scr: -------------------------------> 2)
    
    function ReactDefaultBatchingStrategyTransaction() {
      this.reinitializeTransaction();
    } // scr: ------------------------------------------------------> 1)
    ...
      // scr: ------------------------------------------------------> 3)
    var transaction = new ReactDefaultBatchingStrategyTransaction();
    ...
    
    ReactDefaultBatchingStrategyTransaction@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
    

    1)在ReactDefaultBatchingStrategyTransaction的构造函数中,调用超类Transaction的构造函数,该构造函数使用2)中定义的FLUSH_BATCHED_UPDATES初始化this.transactionWrappers

    2)定义两个包装器以及它们各自的initialize()close(),它们在迭代FLUSH_BATCHED_UPDATES的循环中用在Transaction.initializeAll()Transaction.closeAll()

    3)将ReactDefaultBatchingStrategyTransaction定义为单例。

    最后,我们看一下ReactDefaultBatchingStrategy提供的公共API,可以从外界调用它

    var ReactDefaultBatchingStrategy = {
      isBatchingUpdates: false,
    
      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) { // scr: --------> not applied here
          return callback(a, b, c, d, e);
        } else {
          return transaction.perform(callback, null, a, b, c, d, e);
        }
      },
    };
    
    ReactDefaultBatchingStrategy@renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
    

    ReactDefaultBatchingStrategy作为batchingStrategy注入{第二篇 * 5}到ReactUpdatesReactUpdates.enqueueUpdate()使用ReactDefaultBatchingStrategy.batchedUpdates(),UI更新入口点setState()的基础方法。

    function enqueueUpdate(component) {
      ensureInjected();
    
      if (!batchingStrategy.isBatchingUpdates) { // scr: ----------> {a}
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
    
      // scr: -----------------------------------------------------> {b}
      dirtyComponents.push(component);
      if (component._updateBatchNumber == null) {
        // scr: this field is used for sanity check later
        component._updateBatchNumber = updateBatchNumber + 1;
      }
    }
    
    ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js
    

    这是我们在上一篇文章中看到的类似的递归技巧。

    1)第一次输入该方法时,ReactDefaultBatchingStrategy.isBatchingUpdatesfalse,这会触发分支{a},该分支通向ReactDefaultBatchingStrategy.batchedUpdates();。

    2)batchedUpdates()ReactDefaultBatchingStrategy.isBatchingUpdates设置为true,并初始化transaction

    3)batchedUpdatescallback参数是enqueueUpdate()本身,因此enqueueUpdate将再次使用transaction.perform()再次输入。 请注意,两个包装器的前置方法(initialize())都是emptyFunction,因此两次调用enqueueUpdate()之间什么都没有发生。

    4)当第二次输入enqueueUpdate()时(在刚刚初始化的Transaction上下文中),执行分支{b};

    ...
    dirtyComponents.push(component);
    ...
    

    5)在enqueueUpdate()返回FLUSH_BATCHED_UPDATES的后方法(close())之后; 这是处理前面步骤中标记的所有dirtyComponents的主要方法

    *8 we will come back to this FLUSH_BATCHED_UPDATES.close() and ReactUpdates.flushBatchedUpdates() in the next post
    * 8我们将在下一篇文章中回到 FLUSH_BATCHED_UPDATES.close()ReactUpdates.flushBatchedUpdates()

    6)最后,调用RESET_BATCHED_UPDATES的后方法(close()),这会将ReactDefaultBatchingStrategy.isBatchingUpdates设置为false并完成循环。

    重要的是要注意,应该在ReactDefaultBatchingStrategy.isBatchingUpdates:false的上下文中执行3)和6)之间对enqueueUpdate()的任何后续调用,这意味着在这种情况下将使用分支{b}。 所以就像

    ->dirtyComponents.push(component);
    ->dirtyComponents.push(component);
    ->dirtyComponents.push(component);
    ...
    ----->ReactUpdates.flushBatchedUpdates
    

    Wrap-up

    (原文链接Understanding The React Source Code - UI Updating (Transaction) VI)

    (上一篇)【译】了解React源代码-初始渲染(类组件)5

    (下一篇)【译】了解React源代码-UI更新(事务)7

    相关文章

      网友评论

          本文标题:【译】了解React源代码-UI更新(事务)6

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