美文网首页
【译】了解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