博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React 源码深度解读(七):事务 - Part 1
阅读量:6850 次
发布时间:2019-06-26

本文共 8677 字,大约阅读时间需要 28 分钟。

  • 前言

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

本系列文章基于 React 15.4.2 ,以下是本系列其它文章的传送门:

  • 正文

在阅读 React 源码过程中,transaction 可以说无处不在,所有涉及到 UI 更新相关的操作都会借助 transaction 来完成。下面,我们就来看看它所起到的特殊所用。

  • Transaction 核心实现

Transaction 本质来说只是一个对象,它的核心方法是 perform:

perform: function <        A,        B,        C,        D,        E,        F,        G,        T: (a: A, b: B, c: C, d: D, e: E, f: F) => G // eslint-disable-line space-before-function-paren            >        (            method: T, scope: any,            a: A, b: B, c: C, d: D, e: E, f: F,        ): G {            var errorThrown;            var ret;            try {                this._isInTransaction = true;                // Catching errors makes debugging more difficult, so we start with                // errorThrown set to true before setting it to false after calling                // close -- if it's still set to true in the finally block, it means                // 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) {                        // If `method` throws, prefer to show that stack trace over any thrown                        // by invoking `closeAll`.                        try {                            this.closeAll(0);                        } catch (err) {}                    } else {                        // Since `method` didn't throw, we don't want to silence the exception                        // here.                        this.closeAll(0);                    }                } finally {                    this._isInTransaction = false;                }            }            return ret;        },

可以看到,这个方法只做了 3 件事情:

  1. 执行初始化方法 initializeAll
  2. 执行传入的 callback 方法
  3. 执行收尾方法 closeAll

这里的结构很有意思,有 try 竟然没有 catch,取而代之的是 finally。说明就算抛出了错误,finally 部分的代码也要继续执行,随后再将错误往上层代码抛。这样能保证无论在什么情况下,closeAll 都能得到执行。

下面来看一下结构极其相似的 initializeAll 和 closeAll 方法:

initializeAll: function (startIndex: number): void {    var transactionWrappers = this.transactionWrappers;        for (var i = startIndex; i < transactionWrappers.length; i++) {        var wrapper = transactionWrappers[i];        try {            // Catching errors makes debugging more difficult, so we start with the            // OBSERVED_ERROR state before overwriting it with the real return value            // of initialize -- if it's still set to OBSERVED_ERROR in the finally            // block, it means wrapper.initialize threw.            this.wrapperInitData[i] = OBSERVED_ERROR;            this.wrapperInitData[i] = wrapper.initialize ?                wrapper.initialize.call(this) :                null;        } finally {            if (this.wrapperInitData[i] === OBSERVED_ERROR) {                // The initializer for wrapper i threw an error; initialize the                // remaining wrappers but silence any exceptions from them to ensure                // that the first error is the one to bubble up.                try {                    this.initializeAll(i + 1);                } catch (err) {}            }        }    }},...closeAll: function (startIndex: number): void {    var transactionWrappers = this.transactionWrappers;        for (var i = startIndex; i < transactionWrappers.length; i++) {        var wrapper = transactionWrappers[i];        var initData = this.wrapperInitData[i];        var errorThrown;        try {            // Catching errors makes debugging more difficult, so we start with            // errorThrown set to true before setting it to false after calling            // close -- if it's still set to true in the finally block, it means            // wrapper.close threw.            errorThrown = true;            if (initData !== OBSERVED_ERROR && wrapper.close) {                wrapper.close.call(this, initData);            }            errorThrown = false;        } finally {            if (errorThrown) {                // The closer for wrapper i threw an error; close the remaining                // wrappers but silence any exceptions from them to ensure that the                // first error is the one to bubble up.                try {                    this.closeAll(i + 1);                } catch (e) {}            }        }    }    this.wrapperInitData.length = 0;},

transactionWrappers 是一个数组,一个 transaction 可以有多个 wrapper,通过 reinitializeTransaction 来初始化。每个 wrapper 都需要定义 initialize 和 close 方法。initializeAll 和 closeAll 都能保证其中一个 wrapper 成员抛出错误的时候,余下的 wrapper 能继续执行。initialize 有一个返回值,传给对应的 close 方法。当 initialize 抛出错误的时候,由于没有 catch,exception 会一直往上抛,中断了ret = method.call(scope, a, b, c, d, e, f)的执行去到 finally,接着执行 closeAll。

了解 transaction 的基本概念后,我们来看下它是怎么应用的。

  • ReactDefaultBatchingStrategyTransaction

我们以ReactDefaultBatchingStrategyTransaction为例子来看看 transaction 是怎么用的:

// transaction 子类function ReactDefaultBatchingStrategyTransaction() {  this.reinitializeTransaction();}// 覆盖 transaction 的 getTransactionWrappers 方法Object.assign(  ReactDefaultBatchingStrategyTransaction.prototype,  Transaction,  {    getTransactionWrappers: function() {      return TRANSACTION_WRAPPERS;    },  });var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];var RESET_BATCHED_UPDATES = {  initialize: emptyFunction,  close: function() {    ReactDefaultBatchingStrategy.isBatchingUpdates = false;  },};var FLUSH_BATCHED_UPDATES = {  initialize: emptyFunction,  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),};

首先,ReactDefaultBatchingStrategyTransaction继承了 transaction,并覆盖了getTransactionWrappers这个方法来定义自己的 wrapper。这 2 个 wrapper 很简单,initialize都是空函数,close 的时候就重置下标志位,然后再调用另一个方法。

下面再看一下创建ReactDefaultBatchingStrategyTransaction的对象ReactDefaultBatchingStrategy

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) {            return callback(a, b, c, d, e);        } else {            return transaction.perform(callback, null, a, b, c, d, e);        }    },};

第一步是创建一个 ReactDefaultBatchingStrategyTransaction 实例。当batchedUpdates第一次被调用的时候,alreadyBatchingUpdates为 false,会调用transaction.perform,让后续的操作都处于 transaction 的上下文之中。后面再调用batchedUpdates的时候,只是单纯的执行callback

而调用ReactDefaultBatchingStrategy的是ReactUpdates,它通过依赖注入的方法在运行的时候将ReactDefaultBatchingStrategy注入进去。

function enqueueUpdate(component) {  ensureInjected();  // Various parts of our code (such as ReactCompositeComponent's  // _renderValidatedComponent) assume that calls to render aren't nested;  // verify that that's the case. (This is called by each top-level update  // function, like setState, forceUpdate, etc.; creation and  // destruction of top-level components is guarded in ReactMount.)  if (!batchingStrategy.isBatchingUpdates) {    batchingStrategy.batchedUpdates(enqueueUpdate, component);    return;  }  dirtyComponents.push(component);  if (component._updateBatchNumber == null) {    component._updateBatchNumber = updateBatchNumber + 1;  }}

enqueueUpdate第一次执行的时候,它会检测是否在 batchUpdate 的模式下(batchingStrategy.isBatchingUpdates),如果不是则调用batchingStrategy.batchedUpdates,如果是则执行dirtyComponents.push(component)

当我们使用setState的时候,它会调用ReactUpdatesenqueueSetState,然后再调用enqueueUpdate。如果在 React 的生命周期函数又或者使用 React 自带的合成事件时,会在setState之前先将整个处理过程设置为 batchUpdate 的模式,所以当我们setState的时候,实际上只会执行dirtyComponents.push(component),并不会马上更新 state,这就是为什么setState看似异步更新的原因。实际上它还是同步的。

以 React 生命周期函数为例子,当 Component 被初始化的时候,会执行_renderNewRootComponent

_renderNewRootComponent: function (    nextElement,    container,    shouldReuseMarkup,    context) {    ...    // The initial render is synchronous but any updates that happen during    // rendering, in componentWillMount or componentDidMount, will be batched    // according to the current batching strategy.    ReactUpdates.batchedUpdates(        batchedMountComponentIntoNode,        componentInstance,        container,        shouldReuseMarkup,        context    );    ...},

在这里就预先将整个处理过程设置为 batchUpdate 的模式了,官方的注释也说明了这点。

  • 总结

我们再通过一张图,来总结下 transaction 是怎么被调用的。

clipboard.png

clipboard.png

转载地址:http://fugul.baihongyu.com/

你可能感兴趣的文章
windows 搭建 subversion+TortoiseSVN
查看>>
windows8安装xna4.0不能开发Xbox和PC端游戏的解决办法
查看>>
jQuery.validate errorPlacement
查看>>
转载:linux vi命令详解
查看>>
EM算法原理
查看>>
System.Drawing.Color的颜色对照表
查看>>
一次滚动一屏的滚动条行为实现
查看>>
.NET面试题(三)
查看>>
自定义TreeList单元格 z
查看>>
【百度地图】- 学习.1
查看>>
JS函数重载解决方案
查看>>
Nginx中的rewrite指令
查看>>
CSS系列:CSS3新增选择器
查看>>
IDDD 实现领域驱动设计-一个简单的 CQRS 示例
查看>>
IOS开发基础知识--碎片16
查看>>
Java的HashSet类
查看>>
Putty设置删除
查看>>
圈真的决定你的未来?
查看>>
各种分布式文件系统简介
查看>>
40 - 找出数组中仅仅出现一次的数字
查看>>