react事务机制 - CSDN
精华内容
参与话题
  • React中,组件的更新本质上都是由setState操作改变state引起的。因此组件更新的入口在于setState,同样经过撸源码和打断点分析画了以下的组件更新的流程图: setState的定义在组件mountComponent的时候定义: ...

    在React中,组件的更新本质上都是由setState操作改变state引起的。因此组件更新的入口在于setState,同样经过撸源码和打断点分析画了以下的组件更新的流程图:

    setState的定义在组件mountComponent的时候定义:

    inst = new Component(publicProps, publicContext, ReactUpdateQueue);
    复制代码
    function ReactComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    ReactComponent.prototype.setState = function (partialState, callback) {
      this.updater.enqueueSetState(this, partialState);
      if (callback) {
        this.updater.enqueueCallback(this, callback, 'setState');
      }
    };
    复制代码

    所以setState的真正的定义在 ReactUpdateQueue.js

    enqueueSetState: function (publicInstance, partialState) {
      var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
      if (!internalInstance) {
        return;
      }
      var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
      // 将state添加到对应的component的_pendingStateQueue数组中。
      queue.push(partialState);
      enqueueUpdate(internalInstance);
    }
    
    enqueueCallback: function (publicInstance, callback, callerName) {
      var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
      if (!internalInstance) {
        return null;
      }
      // 将callback添加到对应的component的_pendingCallbacks数组中。
      if (internalInstance._pendingCallbacks) {
        internalInstance._pendingCallbacks.push(callback);
      } else {
        internalInstance._pendingCallbacks = [callback];
      }
      enqueueUpdate(internalInstance);
    }
    复制代码

    两个方法最后都调用enqueueUpdate:

    function enqueueUpdate(internalInstance) {
      ReactUpdates.enqueueUpdate(internalInstance);
    }
    
    function enqueueUpdate(component) {
      // 确认需要的事务是否注入了。
      ensureInjected();
      // batchingStrategy.isBatchingUpdates为false的时候,
      // 或者说当不处于批量更新的时候,用事务的方式批量的进行component的更新。
      if (!batchingStrategy.isBatchingUpdates) {
        batchingStrategy.batchedUpdates(enqueueUpdate, component);
        return;
      }
      // 当处于批量更新阶段时,不进行state的更新操作,而是将需要更新的component添加到dirtyComponents数组中
      dirtyComponents.push(component);
    }
    复制代码

    这里需要注意enqueueUpdate中根据batchingStrategy.isBatchingUpdates分别进入不同的流程,当isBatchingUpdates为true的时候表示已经处于批量更新的过程中了,这时候会将所有的有改动的组件push到dirtyComponents中。当isBatchingUpdates为false的时候会执行更新操作,这里先认为当isBatchingUpdates为false的时候进行的操作是更新组件,实际上的过程是更复杂的,稍后马上解释具体的过程。这里我们先理解下React中的事务的概念,事务的概念根据源码中的注释就可以非常清楚的了解了:

     *                       wrappers (injected at creation time)
     *                                      +        +
     *                                      |        |
     *                    +-----------------|--------|--------------+
     *                    |                 v        |              |
     *                    |      +---------------+   |              |
     *                    |   +--|    wrapper1   |---|----+         |
     *                    |   |  +---------------+   v    |         |
     *                    |   |          +-------------+  |         |
     *                    |   |     +----|   wrapper2  |--------+   |
     *                    |   |     |    +-------------+  |     |   |
     *                    |   |     |                     |     |   |
     *                    |   v     v                     v     v   | wrapper
     *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
     * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
     * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | +---+ +---+   +---------+   +---+ +---+ |
     *                    |  initialize                    close    |
     *                    +-----------------------------------------+
    复制代码

    简单来说当使用transaction.perform执行方法method的时候会按顺序先执行WRAPPER里面的initialize方法然后执行method最后再执行close方法。

    React提供了基础的事务对象Transaction,不同的事务的区别就在于initialize和close方法的不同,这个可以通过定义getTransactionWrappers方法来传入WRAPPER数组,具体的用法看下源码就好了,不过实际使用中是不会要自己去定义事务的,当然要的话也阻止不了~。

    回到enqueueUpdate,其调用的batchingStrategy.batchedUpdates方法在ReactDefaultBatchingStrategy 中定义了:

    var ReactDefaultBatchingStrategy = {
      isBatchingUpdates: false,
      batchedUpdates: function (callback, a, b, c, d, e) {
        var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
        ReactDefaultBatchingStrategy.isBatchingUpdates = true;
        if (alreadyBatchingUpdates) {
          callback(a, b, c, d, e);
        } else {
          transaction.perform(callback, null, a, b, c, d, e);
        }
      }
    };
    复制代码

    可以看到isBatchingUpdates的初始值是false的,在调用batchedUpdates方法的时候会将isBatchingUpdates变量设置为true。然后根据设置之前的isBatchingUpdates的值来执行不同的流程。

    对于enqueueUpdate的效果就是,当执行enqueueUpdate的时候如果isBatchingUpdates为true的话(已经处于批量执行操作),则不会进行更新操作,而是将改动的component添加到dirtyComponents数组中;如果isBatchingUpdates为false的话,会执行batchedUpdates将isBatchingUpdates置为true然后调用enqueueUpdate方法,这个时候会用事务的方式来执行enqueueUpdate。

    根据流程图可以知道,事务ReactDefaultBatchingStrategyTransaction的initialize是foo没有任务操作,接着会执行method即:将改动的组件push到dirtyComponent中,最后执行close方法执行flushBatchedUpdate方法再把isBatchingUpdates重置为false。在flushBatchedUpdates方法中事务执行runBatchedUpdates方法将dirtyComponent中的component依次(先父组件在子组件的顺序)进行更新操作。这里具体的更新的过程看流程图就可以理解了,需要注意的是updateChildren方法这个方法是virtual DOM的Diff算法的核心代码,作用就是根据更新前后组件的不同进行有效的更新,具体的部分,之后单独的文章再介绍。

    在更新的过程中需要注意的一个方法是_processPendingState方法:

    _processPendingState: function (props, context) {
       var inst = this._instance;
       var queue = this._pendingStateQueue;
       var replace = this._pendingReplaceState;
       this._pendingReplaceState = false;
       this._pendingStateQueue = null;
    
       if (!queue) {
         return inst.state;
       }
    
       if (replace && queue.length === 1) {
         return queue[0];
       }
    
       var nextState = _assign({}, replace ? queue[0] : inst.state);
       for (var i = replace ? 1 : 0; i < queue.length; i++) {
         var partial = queue[i];
         _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
       }
    
       return nextState;
     }
    复制代码

    可以看到当setState传入的是函数的时候,函数被调用的时候的传入的参数是merge了已经遍历的queue的state的nextState,如果传入的不是函数则直接merge state至nextState。这也解释了,为什么用回调函数的形式使用setState的时候可以解决state是按照顺序最新的state了。

    从流程图可以看到在保证组件更新完毕后会将setState中传入的callback按照顺序依次push到事务的callback queue队列中,在事务结束的时候close方法中notifyAll就是执行这些callbacks,这样保证了回调函数是在组件完全更新完成后执行的,也就是setState的回调函数传入的state是更新后的state的原因。

    在了解了以上的组件更新的流程后,可以看一个场景,栗子如下:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    ReactDOM.render(<App />, document.getElementById('root'));
    
    import React, { Component } from 'react';
    import Hello from './Hello';
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          appText: 'hello App',
          helloText: 'heiheihei'
        };
      }
    
      handleAppClick = () => {
        console.log('App is clicked ~');
        this.setState({
          appText: 'App is clicked ~'
        });
      }
    
      render() {
        const { appText, helloText } = this.state;
        console.log('render App');
        return (
          <div className="app-container">
            <div          
              onClick={this.handleAppClick}
            >{appText}</div>
            <Hello
              text={helloText}
              handleAppClick={this.handleAppClick}
            />
          </div>
        );
      }
    }
    
    export default App;
    
    import React, { Component } from 'react';
    
    class Hello extends Component {
        constructor(props) {
            super(props);
            this.state = {
                text: 'hello Hello'
            };
        }
    
        componentWillReceiveProps(nextProps) {
            this.setState({
              text: nextProps.text + '~'
            });
        }
    
        handleClick = () => {
            this.setState({
                text: 'Hello is clicked ~'
            });
            this.props.handleAppClick();
        }
    
        render() {
            const { text } = this.state;
            console.log('render Hello');
            return (
                <div>
                    <div
                        onClick={this.handleClick}
                        style={{ color: '#e00' }}
                    >{text}</div>
                </div>
            );
        }
    }
    
    export default Hello;
    复制代码

    点击

    hello Hello
    复制代码

    后组件的渲染如下,可以看到父组件到子组件按顺序更新了一次:

    render App
    render Hello
    复制代码

    而不是:

    render Hello
    render App
    render Hello
    复制代码

    批量更新的时候组件的顺序由:

    dirtyComponents.sort(mountOrderComparator);
    复制代码

    处理的。

    到这里你需要知道这个结果产生的原因在于不是只有setState的调用栈会改变isBatchingUpdates的值

    回顾《React事件机制》的流程图可以知道事件的统一回调函数dispatchEvent调用了ReactUpdates.batchedUpdates用事务的方式进行事件的处理,也就是说点击事件的处理本身就是在一个大的事务中,在setState执行的时候isBatchingUpdates已经是true了,setState做的就是将更新都统一push到dirtyComponents数组中,在事务结束的时候按照上述的流程进行批量更新,然后将批量执行关闭结束事务。

    事务的机制在这里也保证了React更新的效率,此外在更新组件的时候的virtual DOM的Diff算法也起到很大的作用,这个在后续的文章再介绍。

    参考资料

    展开全文
  • react state事务机制

    2019-12-18 19:55:47
    react构建过程中会处于一个大的事务中,先将isBatchingUpdates设置为true,正在处于批量更新的过程,后续的setstate并不会立马被更新而是都会先进入到脏组件中,当该大事务结束时isBatchingUpdates设置为false,即...

    react构建过程中会处于一个大的事务中,先将isBatchingUpdates设置为true,正在处于批量更新的过程,后续的setstate并不会立马被更新而是都会先进入到脏组件中,当该大事务结束时isBatchingUpdates设置为false,即关闭批量更新的标志,然后就是以事务的形式开始执行函数flushBatchedUpdates(真正开始批量更新state),flushBatchedUpdates中又会先以事务执行runBatchedUpdates批量遍历脏组件更新state,然后再以事务执行ReactUpdatesFlushTransaction,这个是执行setstate中的回调函数,这样就保证了在setstate中的回调函数可以拿到最新的state。回调函数是在生命周期执行完毕之后才执行的即componentdidupdate执行完毕。

    https://segmentfault.com/a/1190000010785692

    展开全文
  • 阅读源码成了今年的学习目标之一,在选择 Vue 和 React 之间,我想先阅读 React 。 在考虑到读哪个版本的时候,我想先接触到源码早期的思想可能会更轻松一些,最终我选择阅读 0.3-stable 。 那么接下来,我将从几个...

    阅读源码成了今年的学习目标之一,在选择 Vue 和 React 之间,我想先阅读 React 。 在考虑到读哪个版本的时候,我想先接触到源码早期的思想可能会更轻松一些,最终我选择阅读 0.3-stable 。 那么接下来,我将从几个方面来解读这个版本的源码。

    1. React 源码学习(一):HTML 元素渲染
    2. React 源码学习(二):HTML 子元素渲染
    3. React 源码学习(三):CSS 样式及 DOM 属性
    4. React 源码学习(四):事务机制
    5. React 源码学习(五):事件机制
    6. React 源码学习(六):组件渲染
    7. React 源码学习(七):生命周期
    8. React 源码学习(八):组件更新

    什么是事务

    我们需要直接看到事务的整个概念:

    英文版请看源码,以下为 Google 翻译内容

    Transaction 创建一个黑盒子,它能够包装任何方法,以便在调用方法之前和之后维护某些不变量(即使在调用包装方法时抛出异常)。 实例化事务的人可以在创建时提供不变量的执行者。 Transaction 类本身将为您提供一个额外的自动不变量 - 任何事务实例在运行时不应该运行的不变量。您通常会创建一个 Transaction 的单个实例,以便多次重用,这可能用于包装多个不同的方法。包装器非常简单 - 它们只需要实现两种方法。

    /**
     * <pre>
     *                                 wrappers (创建时注入)
     *                                      +        +
     *                                      |        |
     *                    +-----------------|--------|--------------+
     *                    |                 v        |              |
     *                    |      +---------------+   |              |
     *                    |   +--|    wrapper1   |---|----+         |
     *                    |   |  +---------------+   v    |         |
     *                    |   |          +-------------+  |         |
     *                    |   |     +----|   wrapper2  |--------+   |
     *                    |   |     |    +-------------+  |     |   |
     *                    |   |     |                     |     |   |
     *                    |   v     v                     v     v   | wrapper
     *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
     * perform(任何方法)   | |   | |   |   |         |   |   | |   | | maintained
     * +----------------->|-|---|-|---|-->| 任何方法 |---|---|-|---|-|-------->
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | |   | |   |   |         |   |   | |   | |
     *                    | +---+ +---+   +---------+   +---+ +---+ |
     *                    |  initialize                    close    |
     *                    +-----------------------------------------+
     * </pre>
     */
    复制代码

    奖金:

    • 按方法名称和包装器索引报告时间度量标准。

    用例:

    • 在对帐之前/之后保留输入选择范围。即使出现意外错误也可以恢复选择。
    • 在重新排列DOM时停用事件,防止模糊/焦点,同时保证事后系统重新激活。
    • 在工作线程中进行协调后,将收集的DOM突变队列刷新到主UI线程。
    • 在呈现新内容后调用任何收集的 componentDidRender 回调。
    • (未来用例):包装 ReactWorker 队列的特定刷新以保留 scrollTop (自动滚动感知DOM)。
    • (未来用例):DOM更新之前和之后的布局计算。

    事务性插件 API:

    • 具有返回任何预计算的 initialize 方法的模块。
    • 和一个接受预计算的 close 方法。当包装过程完成或失败时,调用 close

    事务 Transaction

    // utils/Transaction.js
    var Mixin = {
      /**
       * 设置此实例,以便为收集指标做好准备。 这样做是为了使这个设置方法可以在已经初始化的实例上使用,
       * 其方式是在重用时不消耗额外的内存。 如果您决定将此 mixin 的子类化为 "PooledClass" ,那么这将非常有用。
       */
      reinitializeTransaction: function() {
        // this.getTransactionWrappers() 用于获取上面概念中的 wrappers
        // this.transactionWrappers 就是 wrappers
        this.transactionWrappers = this.getTransactionWrappers();
        // 初始化用于存储 initialize 时返回的内容的 this.wrapperInitData
        if (!this.wrapperInitData) {
          this.wrapperInitData = [];
        } else {
          this.wrapperInitData.length = 0;
        }
        // 时间方面,这里不做解读
        if (!this.timingMetrics) {
          this.timingMetrics = {};
        }
        this.timingMetrics.methodInvocationTime = 0;
        if (!this.timingMetrics.wrapperInitTimes) {
          this.timingMetrics.wrapperInitTimes = [];
        } else {
          this.timingMetrics.wrapperInitTimes.length = 0;
        }
        if (!this.timingMetrics.wrapperCloseTimes) {
          this.timingMetrics.wrapperCloseTimes = [];
        } else {
          this.timingMetrics.wrapperCloseTimes.length = 0;
        }
        // 初始化事务标记
        this._isInTransaction = false;
      },
      _isInTransaction: false,
      getTransactionWrappers: null,
      isInTransaction: function() {
        return !!this._isInTransaction;
      },
      // 在安全窗口内执行该功能。 将此用于顶级方法,这些方法会导致需要进行安全检查的大量计算/突变。
      perform: function(method, scope, a, b, c, d, e, f) {
        throwIf(this.isInTransaction(), DUAL_TRANSACTION);
        var memberStart = Date.now();
        // 报错存储
        var err = null;
        // method 返回内容
        var ret;
        try {
          // 开始 initialize
          this.initializeAll();
          // initialize 完成后调用 method
          ret = method.call(scope, a, b, c, d, e, f);
        } catch (ie_requires_catch) {
          // 抓报错,这里报错会被覆盖
          err = ie_requires_catch;
        } finally {
          var memberEnd = Date.now();
          this.methodInvocationTime += (memberEnd - memberStart);
          try {
            // 开始 close
            this.closeAll();
          } catch (closeAllErr) {
            // 抓报错,这里若前面存在报错,则取前面的报错
            err = err || closeAllErr;
          }
        }
        // 抛出报错
        if (err) {
          throw err;
        }
        // 返回 method 执行结果
        return ret;
      },
      initializeAll: function() {
        // 事务开始
        this._isInTransaction = true;
        var transactionWrappers = this.transactionWrappers;
        var wrapperInitTimes = this.timingMetrics.wrapperInitTimes;
        var err = null;
        // 遍历 wrappers
        for (var i = 0; i < transactionWrappers.length; i++) {
          var initStart = Date.now();
          var wrapper = transactionWrappers[i];
          try {
            // 执行 initialize ,并把返回值存入对应的 this.wrapperInitData[i]
            this.wrapperInitData[i] =
              wrapper.initialize ? wrapper.initialize.call(this) : null;
          } catch (initErr) {
            err = err || initErr;  // Remember the first error.
            // 有报错的话对应位置存入 Transaction.OBSERVED_ERROR
            this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
          } finally {
            var curInitTime = wrapperInitTimes[i];
            var initEnd = Date.now();
            wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart);
          }
        }
        // 抛出错误
        if (err) {
          throw err;
        }
      },
      /**
       * 调用 `this.transactionWrappers.close[i]`  函数中的每一个,向它们传递
       * `this.transactionWrappers.init[i]` 的相应返回值(对应于失败的初始值设定项的 `close`rs 将不被调用)。
       */
      closeAll: function() {
        throwIf(!this.isInTransaction(), MISSING_TRANSACTION);
        var transactionWrappers = this.transactionWrappers;
        var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes;
        var err = null;
        for (var i = 0; i < transactionWrappers.length; i++) {
          var wrapper = transactionWrappers[i];
          var closeStart = Date.now();
          // 获取 initialize 时对应的返回值
          var initData = this.wrapperInitData[i];
          try {
            // 返回值不等于 Transaction.OBSERVED_ERROR 时才执行 close
            if (initData !== Transaction.OBSERVED_ERROR) {
              wrapper.close && wrapper.close.call(this, initData);
            }
          } catch (closeErr) {
            err = err || closeErr;  // Remember the first error.
          } finally {
            var closeEnd = Date.now();
            var curCloseTime = wrapperCloseTimes[i];
            wrapperCloseTimes[i] = (curCloseTime || 0) + (closeEnd - closeStart);
          }
        }
        // 释放内存
        this.wrapperInitData.length = 0;
        // 事务关闭
        this._isInTransaction = false;
        // 抛出错误
        if (err) {
          throw err;
        }
      }
    };
    
    var Transaction = {
      Mixin: Mixin,
      OBSERVED_ERROR: {}
    };
    复制代码

    React 事务

    上面这段代码看完,我相信你对事务的整个概念已经有所了解,那么事务到底在处理什么的时候用到了呢?接下来就要揭晓本次同样重点的内容 React 调度事务, ReactReconcileTransaction

    // core/ReactReconcileTransaction.js
    // 这个就是 React 调度事务的 wrappers
    var TRANSACTION_WRAPPERS = [
      // 确保在可能的情况下,执行事务不会干扰选择范围(当前选定的文本输入)。
      // 通俗点讲就是,在 React 调度事务开始之时将选择的文本信息获取,再结束时还原选择信息。
      // 关于 Selection 的具体实现这里不做解读。
      SELECTION_RESTORATION,
      // 抑制由于高级别的DOM操作(如临时从DOM中删除文本输入)而可能无意中调度的事件(模糊/焦点)。
      // 通俗点讲就是,在 React 调度事务结束之前,抑制事件的传播。
      // 于 core/ReactEventTopLevelCallback.js 中 createTopLevelCallback 函数,关于 _topLevelListenersEnabled 的判断来抑制。
      // 事件控制,稍后做简单解读,但内容不会提及事件具体实现相关。
      EVENT_SUPPRESSION,
      // 提供 `ReactOnDOMReady` 队列,用于在执行事务期间收集 `onDOMReady` 回调。
      ON_DOM_READY_QUEUEING
    ];
    
    function ReactReconcileTransaction() {
      // 初始化事务
      this.reinitializeTransaction();
      // 这里涉及到 PooledClass
      // 看到 ReactOnDOMReady 时末端也有这样一句话:
      // PooledClass.addPoolingTo(ReactOnDOMReady)
      // 调用 getPooled 方法其实就是 new
      // 具体在下面解读 PooledClass
      this.reactOnDOMReady = ReactOnDOMReady.getPooled(null);
    }
    
    var Mixin = {
      // reinitializeTransaction 方法中使用,用于获取 React 调度事务的 wrappers
      getTransactionWrappers: function() {
        // 判断执行环境是否可以使用 DOM
        if (ExecutionEnvironment.canUseDOM) {
          return TRANSACTION_WRAPPERS;
        } else {
          return [];
        }
      },
      getReactOnDOMReady: function() {
        return this.reactOnDOMReady;
      },
      // 这里也牵扯到 PooledClass
      destructor: function() {
        // PooledClass 中 standardReleaser 方法
        ReactOnDOMReady.release(this.reactOnDOMReady);
        // 释放内存,同样的,在 ReactOnDOMReady 中也进行了操作: this._queue = null
        this.reactOnDOMReady = null;
      }
    };
    
    mixInto(ReactReconcileTransaction, Transaction.Mixin);
    mixInto(ReactReconcileTransaction, Mixin);
    
    // 这是一个加入 PooledClass 的方法
    PooledClass.addPoolingTo(ReactReconcileTransaction);
    复制代码

    在看完 ReactReconcileTransaction 其实似懂非懂,没关系,接下来要依次解读 PooledClassReactOnDOMReady

    工具方法 PooledClass

    以下关于 PooledClass 仅解读一个参数的方法,其余多参数情况都是一样的。

    // utils/PooledClass.js
    var oneArgumentPooler = function(copyFieldsFrom) {
      // 相当于执行 CopyConstructor.getPooled()
      // CopyConstructor 就是 addPoolingTo 传入的第一参数
      var Klass = this;
      // 一但执行过 release , CopyConstructor.instancePool.length 才会大于 0
      if (Klass.instancePool.length) {
        // 这个 instance 是做什么用的,相信看到 call ,你变会理解。
        // 用来绑定执行上下文。
        var instance = Klass.instancePool.pop();
        // 这里不再进行实例化,而是以 instance 为执行上下文直接调用,并将其返回。
        Klass.call(instance, copyFieldsFrom);
        return instance;
      } else {
        // 相当于直接 new CopyConstructor(copyFieldsFrom)
        return new Klass(copyFieldsFrom);
      }
    };
    
    var standardReleaser = function(instance) {
      var Klass = this;
      // 在 CopyConstructor.getPooled() 后,保存返回结果
      // 在 release 时作为参数传入,若存在 destructor 则执行。
      if (instance.destructor) {
        instance.destructor();
      }
      // 长度未超过默认大小,则将 instance 推入数组
      if (Klass.instancePool.length < Klass.poolSize) {
        Klass.instancePool.push(instance);
      }
    };
    
    // 默认的大小
    var DEFAULT_POOL_SIZE = 10;
    // 默认的 getPooled 方法
    var DEFAULT_POOLER = oneArgumentPooler;
    
    // 直接在 CopyConstructor 上添加属性的方法,并返回 CopyConstructor
    var addPoolingTo = function(CopyConstructor, pooler) {
      var NewKlass = CopyConstructor;
      NewKlass.instancePool = [];
      NewKlass.getPooled = pooler || DEFAULT_POOLER;
      if (!NewKlass.poolSize) {
        NewKlass.poolSize = DEFAULT_POOL_SIZE;
      }
      NewKlass.release = standardReleaser;
      return NewKlass;
    };
    
    var PooledClass = {
      addPoolingTo: addPoolingTo,
      oneArgumentPooler: oneArgumentPooler,
      twoArgumentPooler: twoArgumentPooler,
      fiveArgumentPooler: fiveArgumentPooler
    };
    复制代码

    要说 PooledClass 是做什么用的,具体我也还是没有 get 到,但是经过我的实践,在调用 var c = CopyConstructor.getPooled() 后,若在 c 上添加属性,如: c.v = '0.3' 。并且在调用 CopyConstructor.release(c) 这样的情况,在重新进行 CopyConstructor.getPooled() 时,这个 v 属性及值任然存在,当然前提是,你自己定义的 destructor 方法里不会销毁 v 的值。

    React DOM 准备完成后的执行队列

    那么现在我们来看下 ReactOnDOMReady

    // core/ReactOnDOMReady.js
    // 初始化队列
    function ReactOnDOMReady(initialCollection) {
      this._queue = initialCollection || null;
    }
    
    mixInto(ReactOnDOMReady, {
      // 入队
      enqueue: function(component, callback) {
        this._queue = this._queue || [];
        // 组件及回调
        this._queue.push({component: component, callback: callback});
      },
      // 执行队列
      notifyAll: function() {
        var queue = this._queue;
        if (queue) {
          // 清空队列
          this._queue = null;
          for (var i = 0, l = queue.length; i < l; i++) {
            var component = queue[i].component;
            var callback = queue[i].callback;
            // 回调传入组件,上下文绑定 component
            // 关于 getDOMNode 方法稍后解读(获得当前 component 的 node)
            callback.call(component, component.getDOMNode());
          }
          queue.length = 0;
        }
      },
      // 清空队列
      reset: function() {
        this._queue = null;
      },
      // PooledClass.release 时候使用
      destructor: function() {
        this.reset();
      }
    });
    
    // 添加 PooledClass 方法
    PooledClass.addPoolingTo(ReactOnDOMReady);
    复制代码

    和事务结合

    这段代码很简单了, ReactOnDOMReady 就是一个执行队列。回顾到上面代码发现, React 调度事务在被初始化的时候,同样 ReactOnDOMReady 也被初始化,在调度事务执行 wrappers 的过程时, ReactOnDOMReady 被相继执行。这个具体的过程,你看到 ON_DOM_READY_QUEUEING 变会明白。

    // core/ReactReconcileTransaction.js
    var ON_DOM_READY_QUEUEING = {
      // 在 React 调度事务 initialize 时, ReactOnDOMReady 队列被重置。
      initialize: function() {
        this.reactOnDOMReady.reset();
      },
      // 在 React 调度事务 close 时, ReactOnDOMReady 队列被依次执行。
      close: function() {
        this.reactOnDOMReady.notifyAll();
      }
    };
    复制代码

    同样的,在 React 调度事务执行 release 时, ReactOnDOMReady 也会执行 release

    getDOMNode 方法

    差点忘了提下, getDOMNode 方法是做什么用的,请直接看到源码:

    // core/ReactComponent.js
    var ReactComponent = {
      Mixin: {
        getDOMNode: function() {
          // 尝试获得 _rootNode
          var rootNode = this._rootNode;
          if (!rootNode) {
            // 尝试获得 _rootNodeID
            rootNode = document.getElementById(this._rootNodeID);
            if (!rootNode) {
              // TODO: Log the frequency that we reach this path.
              // 这里代码就不做详细解读了,反正就是为了获得对应的 Node ,一级级往下查找。
              rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
            }
            // 对其进行赋值,用于下次查询。
            this._rootNode = rootNode;
          }
          return rootNode;
        },
      }
    };
    复制代码

    React 事务中的事件控制

    那么现在,我们来看到 SELECTION_RESTORATION

    // core/ReactReconcileTransaction.js
    var EVENT_SUPPRESSION = {
      initialize: function() {
        // 获取 ReactEvent.isEnabled() 用于 close 时接收。
        // 其实就是 true
        var currentlyEnabled = ReactEvent.isEnabled();
        // 设置为 false
        ReactEvent.setEnabled(false);
        return currentlyEnabled;
      },
      // close 时设置为 true
      close: function(previouslyEnabled) {
        ReactEvent.setEnabled(previouslyEnabled);
      }
    };
    复制代码

    那么在这个真个 wrappers 执行过程,这个有什么用呢? 这需要看到 ReactEventTopLevelCallback 的一段分支逻辑:

    // core/ReactEventTopLevelCallback.js
    var ReactEventTopLevelCallback = {
      createTopLevelCallback: function(topLevelType) {
        return function(fixedNativeEvent) {
          // setEnabled 方法修改的就是 _topLevelListenersEnabled 的值。
          if (!_topLevelListenersEnabled) {
            return;
          }
          // 直接掉过后续逻辑
        };
      }
    };
    复制代码

    回顾渲染 HTML 元素事务调度过程

    那么到此,我们来回顾一下,之前说到的事务机制的运用是如何进行的,重新看到这段代码是不是会清晰了很多:

    // core/ReactComponent.js
    var ReactComponent = {
      Mixin: {
        mountComponentIntoNode: function(rootID, container) {
          // 初始化 React 调度事务
          var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
          // 进入 React 调度事务 wrappers 环节
          transaction.perform(
            this._mountComponentIntoNode,
            this,
            rootID,
            container,
            transaction
          );
          // 销毁这个 React 调度事务
          ReactComponent.ReactReconcileTransaction.release(transaction);
          // 整个挂载方法结束
        }
      }
    }
    复制代码

    这里附一张此方法的图:

    /**
     * <pre>
     *                    TRANSACTION_WRAPPERS (ExecutionEnvironment.canUseDOM 为 true 的情况)
     *                                       +                +     +
     *                                       |                |     |
     *                     +-----------------|----------------|-----|-------------------+
     *                     |                 v                |     |                   |
     *                     |      +-----------------------+   |     |                   |
     *                     |   +--| SELECTION_RESTORATION |---|-----|---+               |
     *                     |   |  +-----------------------+   v     |   |               |
     *                     |   |             +-------------------+  |   |               |
     *                     |   |     +-------| EVENT_SUPPRESSION |--|---|-----+         |
     *                     |   |     |       +-------------------+  v   |     |         |
     *                     |   |     |        +-----------------------+ |     |         |
     *                     |   |     |     +--| ON_DOM_READY_QUEUEING |-|-----|-----+   |
     *                     |   |     |     |  +-----------------------+ |     |     |   |
     *                     |   |     |     |                            |     |     |   |
     * perform(_mount      |   v     v     v                            v     v     v   | wrapper
     *         Component   | +---+ +---+ +---+   +----------------+   +---+ +---+ +---+ | invariants
     *         IntoNode)   | |   | |   | |   |   |                |   |   | |   | |   | | maintained
     * +------------------>|-|---|-|---|-|---|-->|     _mount     |---|---|-|---|-|---|-|-------->
     *                     | |   | |   | |   |   |    Component   |   |   | |   | |   | |
     *                     | |   | |   | |   |   |    IntoNode    |   |   | |   | |   | |
     *                     | |   | |   | |   |   |                |   |   | |   | |   | |
     *                     | +---+ +---+ +---+   +----------------+   +---+ +---+ +---+ |
     *                     |     initialize                                 close       |
     *                     +------------------------------------------------------------+
     * </pre>
     */
    复制代码

    那么至此,实现事务机制及 React 调度事务。


    关于 Reconciler

    Stack Reconciler

    我们知道浏览器渲染引擎是单线程的,在 React 15.x 版本及之前版本,计算组件树变更时将会阻塞整个线程,整个渲染过程是连续不中断完成的,而这时的其他任务都会被阻塞,如动画等,这可能会使用户感觉到明显卡顿,比如当你在访问某一网站时,输入某个搜索关键字,更优先的应该是交互反馈或动画效果,如果交互反馈延迟 200ms ,用户则会感觉较明显的卡顿,而数据响应晚200毫秒并没太大问题。这个版本的调和器可以称为栈调和器( Stack Reconciler ),其调和算法大致过程见 React Diff 算法React Stack Reconciler 实现

    Stack Reconcilier 的主要缺陷就是不能暂停渲染任务,也不能切分任务,无法有效平衡组件更新渲染与动画相关任务间的执行顺序,即不能划分任务优先级,有可能导致重要任务卡顿,动画掉帧等问题。

    Fiber Reconciler

    React 16 版本提出了一个更先进的调和器,它允许渲染进程分段完成,而不必须一次性完成,中间可以返回至主进程控制执行其他任务。而这是通过计算部分组件树的变更,并暂停渲染更新,询问主进程是否有更高需求的绘制或者更新任务需要执行,这些高需求的任务完成后才开始渲染。这一切的实现是在代码层引入了一个新的数据结构 - Fiber 对象,每一个组件实例对应有一个 fiber 实例,此 fiber 实例负责管理组件实例的更新,渲染任务及与其他 fiber 实例的联系。

    这个新推出的调和器就叫做纤维调和器( Fiber Reconciler ),它提供的新功能主要有:

    1. 可切分,可中断任务;
    2. 可重用各分阶段任务,且可以设置优先级;
    3. 可以在父子组件任务间前进后退切换任务;
    4. render 方法可以返回多元素(即可以返回数组);
    5. 支持异常边界处理异常;

    参阅《React Fiber初探》

    展开全文
  • React事务机制解析

    万次阅读 2019-01-11 23:28:57
    react有一定了解的开发人员应该都听说过react事务机制,这个机制几乎贯穿于react所有提供的方法,包括react中最常使用的setState函数 那么,react事务机制到底是一种什么样的机制呢,为了解释事务机制的实现...

    对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方法里面处理异常,官方的说法是捕获错误会使调试变的困难,具体原因应该是因为作为基础的原型类,此处不应该去捕获外部传入方法抛出的异常,应该让异常直接抛回外部,由外部函数自行处理,如果此处强行将异常捕获,确实会导致外部函数调试变的比较困难

    展开全文
  • react事务原理

    2019-10-17 19:27:16
    react事务原理react事务的基本原理实现 react事务的基本原理 就是在执行目标代码之前,之后可以添加很多自己的逻辑 将目标代码经过自身的perform的封装可以在执行前后添加一组或者多组逻辑方法 实现 class ...
  • react虚拟DOM的机制

    万次阅读 2019-08-04 10:37:51
    虚拟DOM的机制: 在浏览器端用JavaScript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行...
  • React事务的一些理解

    2019-03-20 03:18:58
    学习React有一段时间了,刚接触不久对于React神奇的操作很好奇,迫不及待看了源码看过几遍源码总是一知半解,最近有时间再次学习React...那就是React内部的事务,个人觉得事务很重要,生命周期中的componentWillMoun...
  • 写在前面上个月有幸研究了 react 事件机制这个知识点,并且在公司内部把自己的理解进行了分享。现在趁还算热乎赶紧的整理下来,留住这个长脸的时刻。大纲主要分为4大块儿,主...
  • 大纲主要分为4大块儿,主要是结合源码对 react事件机制的原理 进行分析,希望可以让你对 react事件机制有更清晰的认识和理解。当然肯定会存在一些表述不清或者理解不够...
  • 本文来说下 react 事件机制的注册过程,在这个过程中主要经过了哪些关键步骤。文章涉及到的源码是基于 react15.6.1版本,虽然不是最新版本但是也不会影响我们对 ...
  • React源码分析5 — setState机制

    万次阅读 2017-03-02 12:01:52
    1 概述React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定。...React中利用队列机制来管理state,避免了很多重复的View刷新。下面我们来从源码角度探寻下setSt
  • React深入】setState的执行机制一.几个开发中经常会遇到的问题 以下几个问题是我们在实际开发中经常会遇到的场景,下面用几个简单的示例代码来还原一下。 1.setState是同步还是异步的,为什么有的时候不能立即拿到...
  • React 把组件看作状态机(有限状态机), 使用state来控制本地状态, 使用props来传递状态. 前面我们探讨了 React 如何映射状态到 UI 上(初始渲染), 那么接下来我们谈谈 React 时如何同步状态到 UI 上的, 也就是: React...
  • 在前两篇文章中,我们分析了React组件的实现...因为之前的文章对于这些我们一笔带过,所以本篇我们基于大家都再熟悉不过的setState方法来探究事务机制和更新队列。 1.setState相关 在第一篇文章《React源码解析(一):...
  • Spring Framework最近宣布将提供对React事务管理的支持 。 让我们深入研究它对R2DBC(SQL数据库访问的React式规范)如何工作。 事务管理是一种模式,而不是特定于技术的。 从这个角度来看,它的属性和运行时...
  • 本文继续接上一篇文章涉及到的源码是基于 react15.6.1版本,虽然不是最新版本但是也不会影响我们对 react 事件机制的整体把握和理解。先简单的回顾下上一文,最...
  • 源码看React 事件机制

    2019-10-03 03:02:44
    React熟悉的同学都知道,React中的事件机制并不是原生的那一套,事件没有绑定在原生DOM上,发出的事件也是对原生事件的包装。那么这一切是怎么实现的呢? 事件注册 首先还是看我们熟悉的代码 &lt;button ...
  • React源码分析7 — React合成事件系统

    万次阅读 2017-03-10 23:37:16
    1 React合成事件特点React自己实现了一套高效的事件注册,存储,分发和重用逻辑,在DOM事件体系基础上做了很大改进,减少了内存消耗,简化了事件逻辑,并最大化的解决了IE等浏览器的不兼容问题。与DOM事件体系相比,...
1 2 3 4 5 ... 20
收藏数 2,283
精华内容 913
热门标签
关键字:

react事务机制