资讯专栏INFORMATION COLUMN

React 源码深度解读(八):事务 - Part 2

airborne007 / 1169人阅读

摘要:前言是一个十分庞大的库,由于要同时考虑和,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。

前言

React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过程。在学习 React 源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。本文会大量用到原文中的例子,想体会原汁原味的感觉,推荐阅读原文。

本系列文章基于 React 15.4.2 ,以下是本系列其它文章的传送门:
React 源码深度解读(一):首次 DOM 元素渲染 - Part 1
React 源码深度解读(二):首次 DOM 元素渲染 - Part 2
React 源码深度解读(三):首次 DOM 元素渲染 - Part 3
React 源码深度解读(四):首次自定义组件渲染 - Part 1
React 源码深度解读(五):首次自定义组件渲染 - Part 2
React 源码深度解读(六):依赖注入
React 源码深度解读(七):事务 - Part 1
React 源码深度解读(八):事务 - Part 2
React 源码深度解读(九):单个元素更新
React 源码深度解读(十):Diff 算法详解

正文

上一篇文章介绍了 transaction 的基本概念和用法。今天我们将讲解在更新过程中,React 是如何通过多个 transacion 之间的协作,来有效组织代码的。

二、ReactUpdatesFlushTransaction

前文讲到ReactDefaultBatchingStrategy close 的时候,会调用ReactUpdates.flushBatchedUpdates

PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);

var flushBatchedUpdates = function () {
    while (dirtyComponents.length || asapEnqueued) {
        if (dirtyComponents.length) {
            var transaction = ReactUpdatesFlushTransaction.getPooled();
            transaction.perform(runBatchedUpdates, null, transaction);
            ReactUpdatesFlushTransaction.release(transaction);
        }

        if (asapEnqueued) {
            asapEnqueued = false;
            var queue = asapCallbackQueue;
            asapCallbackQueue = CallbackQueue.getPooled();
            queue.notifyAll();
            CallbackQueue.release(queue);
        }
    }
};

这里又调用了另一个 transaction 来处理后续的流程,有所不同的是 transaction 的创建不是直接 new,而是调用getPooled方法。这个方法是通过前面的PooledClass.addPoolingTo注入到ReactUpdatesFlushTransaction中的,如果对这个步骤感兴趣可以看看这篇文章。下面来看ReactUpdatesFlushTransaction的内容:

Object.assign(
    ReactUpdatesFlushTransaction.prototype,
    Transaction, 
    {
        getTransactionWrappers: function () {
            return TRANSACTION_WRAPPERS;
        },

        destructor: function () {
            this.dirtyComponentsLength = null;
            CallbackQueue.release(this.callbackQueue);
            this.callbackQueue = null;
            ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
            this.reconcileTransaction = null;
        },

        perform: function (method, scope, a) {
            return Transaction.perform.call(
                this,
                this.reconcileTransaction.perform,
                this.reconcileTransaction,
                method,
                scope,
                a
            );
        },
    }
);

var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];

var NESTED_UPDATES = {
    initialize: function () {
        this.dirtyComponentsLength = dirtyComponents.length;
    },
    close: function () {
        if (this.dirtyComponentsLength !== dirtyComponents.length) {
            // Additional updates were enqueued by componentDidUpdate handlers or
            // similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
            // these new updates so that if A"s componentDidUpdate calls setState on
            // B, B will update before the callback A"s updater provided when calling
            // setState.
            dirtyComponents.splice(0, this.dirtyComponentsLength);
            flushBatchedUpdates();
        } else {
            dirtyComponents.length = 0;
        }
    },
};

var UPDATE_QUEUEING = {
    initialize: function () {
        this.callbackQueue.reset();
    },
    close: function () {
        this.callbackQueue.notifyAll();
    },
};

ReactUpdatesFlushTransaction覆盖了原型链上的perform方法,不是直接调用 callback,而是嵌套调用了this.reconcileTransaction.perform,在将 callback 透传给reconcileTransactionperform。这里的reconcileTransaction也开启了实例池。

这里要注意下NESTED_UPDATES这个 wrapper,如果dirtyComponents的数量跟 transaction 开始的时候不一样,它又会递归调用flushBatchedUpdates,直到dirtyComponents不再变化为止。UPDATE_QUEUEING这个 wrapper 暂时先忽略。

目前为止的调用关系如下:

三、ReactReconcileTransaction

ReactReconcileTransaction是一个普通的 transaction,定义了一些 DOM 操作相关的 wrapper:

function ReactReconcileTransaction(useCreateElement: boolean) {
  this.reinitializeTransaction();
  this.renderToStaticMarkup = false;
  this.reactMountReady = CallbackQueue.getPooled(null);
  this.useCreateElement = useCreateElement;
}

var Mixin = {
  getTransactionWrappers: function() {
    return TRANSACTION_WRAPPERS;
  },

  ...

  destructor: function() {
    CallbackQueue.release(this.reactMountReady);
    this.reactMountReady = null;
  },
};


Object.assign(ReactReconcileTransaction.prototype, Transaction, Mixin);

PooledClass.addPoolingTo(ReactReconcileTransaction);

var TRANSACTION_WRAPPERS = [
  SELECTION_RESTORATION,
  EVENT_SUPPRESSION,
  ON_DOM_READY_QUEUEING,
];

var SELECTION_RESTORATION = {
  /**
   * @return {Selection} Selection information.
   */
  initialize: ReactInputSelection.getSelectionInformation,
  /**
   * @param {Selection} sel Selection information returned from `initialize`.
   */
  close: ReactInputSelection.restoreSelection,
};

/**
 * Suppresses events (blur/focus) that could be inadvertently dispatched due to
 * high level DOM manipulations (like temporarily removing a text input from the
 * DOM).
 */
var EVENT_SUPPRESSION = {
  /**
   * @return {boolean} The enabled status of `ReactBrowserEventEmitter` before
   * the reconciliation.
   */
  initialize: function() {
    var currentlyEnabled = ReactBrowserEventEmitter.isEnabled();
    ReactBrowserEventEmitter.setEnabled(false);
    return currentlyEnabled;
  },

  /**
   * @param {boolean} previouslyEnabled Enabled status of
   *   `ReactBrowserEventEmitter` before the reconciliation occurred. `close`
   *   restores the previous value.
   */
  close: function(previouslyEnabled) {
    ReactBrowserEventEmitter.setEnabled(previouslyEnabled);
  },
};

/**
 * Provides a queue for collecting `componentDidMount` and
 * `componentDidUpdate` callbacks during the transaction.
 */
var ON_DOM_READY_QUEUEING = {
  /**
   * Initializes the internal `onDOMReady` queue.
   */
  initialize: function() {
    this.reactMountReady.reset();
  },

  /**
   * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
   */
  close: function() {
    this.reactMountReady.notifyAll();
  },
};

这三个 wrapper 的作用注释都讲得很清楚了,不再赘述。值得一提的是这里perform的 callback 是ReactUpdatesFlushTransaction透传过来的ReactUpdate.runBatchedUpdates

目前为止的调用关系如下:

四、总结

到此为止,transaction 相关的内容就讲完了,下一篇开始介绍真正的更新操作。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99092.html

相关文章

  • React 源码深度解读(三):首次 DOM 元素渲染 - Part 3

    摘要:在学习源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。到此为止,首次渲染就完成啦总结从启动到元素渲染到页面,并不像看起来这么简单,中间经历了复杂的层级调用。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非常艰辛的过...

    U2FsdGVkX1x 评论0 收藏0
  • React 源码深度解读(四):首次自定义组件渲染 - Part 1

    摘要:本篇开始介绍自定义组件是如何渲染的。组件将自定义组件命名为,结构如下经过编译后,生成如下代码构建顶层包装组件跟普通元素渲染一样,第一步先会执行创建为的。调用顺序已在代码中注释。先看图,这部分内容将在下回分解 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读其源码是一个非...

    Warren 评论0 收藏0
  • React 源码深度解读(六):依赖注入

    摘要:依赖注入和控制反转,这两个词经常一起出现。一句话表述他们之间的关系依赖注入是控制反转的一种实现方式。而两者有大量的代码都是可以共享的,这就是依赖注入的使用场景了。下一步就是创建具体的依赖内容,然后注入到需要的地方这里的等于这个对象。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级...

    glumes 评论0 收藏0
  • React 源码深度解读(七):事务 - Part 1

    摘要:在学习源码的过程中,给我帮助最大的就是这个系列文章,于是决定基于这个系列文章谈一下自己的理解。说明就算抛出了错误,部分的代码也要继续执行,随后再将错误往上层代码抛。和都能保证其中一个成员抛出错误的时候,余下的能继续执行。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级非常深,阅读...

    KavenFan 评论0 收藏0
  • React 源码深度解读(二):首次 DOM 元素渲染 - Part 2

    摘要:本文将要讲解的调用栈是下面这个样子的平台无关相关如果看源码,我们会留意到很多相关的代码,我们暂时先将其忽略,会在后续的文章中进行讲解。现在我们来看一下各实例间的关系目前为止的调用栈平台无关相关下一篇讲解三总结本文讲解了转化为的过程。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 R...

    wean 评论0 收藏0

发表评论

0条评论

airborne007

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<