先来几个例子热热身:

.........

constructor(props){
    super(props);
    this.state = {
        index: 0
    }
}

componentDidMount() {
    this.setState({
         index: this.state.index + 1  
    });
    console.log(this.state.index);
    this.setState({
         index: this.state.index + 1  
    });
    console.log(this.state.index);
    setTimeout(() => {
       this.setState({
         index: this.state.index + 1  
        });
        console.log(this.state.index);
       this.setState({
         index: this.state.index + 1  
        });
        console.log(this.state.index);
    }, 400)
}      

.....

上面打印出来是什么?

这个例子网上比较多,基本上详解setState机制的用的都是这个,正解是0,0,2,3

一个例子一般满足不了我,于是我就多试了几个,再看下一个:

componentDidMount() {
    this.setState({
         index: this.state.index + 1  
    });
    console.log(this.state.index);
    setTimeout(() => {
       this.setState({
         index: this.state.index + 1  
        });
        console.log(this.state.index);
       this.setState({
         index: this.state.index + 1  
        });
        console.log(this.state.index);
    }, 400)
}  

这个打印出来是什么?正解是0,2,3,再来一个

componentDidMount() {
    this.setState({
         index: this.state.index + 1  
    });
    console.log(this.state.index);
}  

这个打印出来是什么?你是不是突然自信,这肯定是1了吧,实际上打印出来还是0,mmp,

还有很多问题,比如为什么setState会触发更新,为什么有些生命周期中不能调用setState,或者调用了没效果,setState为什么是异步的,等等问题,可能我们都知道答案,但是却不知道为什么,如果仅仅是为了开发需要,那么你记住答案就行了,但是如果想真正理解它,就必须挖它的源码!

OK,直接来看源码(以下源码均为React v15.0.0中的,可上GitHub查看,我只贴部分用到的)

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
    typeof partialState === 'function' ||
    partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
    'function which returns an object of state variables.'
  );
  if (__DEV__) {
    ReactInstrumentation.debugTool.onSetState();
    warning(
      partialState != null,
      'setState(...): You passed an undefined or null state object; ' +
      'instead, use forceUpdate().'
    );
  }
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

setState函数允许接受两个参数,第一个是partialState,它可以是一个Object,也可以是一个function,也可以是一个空对象指针null,(这里的invariant是一个库,它的用法就是如果不满足第一个参数,则打印后面的错误信息,通常是开发环境中使用),中间部分pass,直接看底部,会发现有两个函数,enqueueSetState和enqueueCallback,enqueueSetState传两个参数,一个是this,还有一个是partialState,就是我们传递进去的setState第一个对象参数;而enqueueCallback是在setState如果有第二个参数callback的时候才会去执行,传入当前组件对象、callback函数和'setState'。我在控制台进行了断点调试,看一下这里传给这两个函数的this指什么:

 

可以看出这里的this就是当前所在的App组件,可以看到它的原型__proto__就是ReactComponent。

那么这两个函数定义是什么呢?再看下面的源码

enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState'
    );

    if (!internalInstance) {
      return;
    }

    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
},

..........

enqueueCallback: function(publicInstance, callback, callerName) {
    ReactUpdateQueue.validateCallback(callback, callerName);
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);

    // Previously we would throw an error if we didn't have an internal
    // instance. Since we want to make it a no-op instead, we mirror the same
    // behavior we have in other enqueue* methods.
    // We also need to ignore callbacks in componentWillMount. See
    // enqueueUpdates.
    if (!internalInstance) {
      return null;
    }

    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    // TODO: The callback here is ignored when setState is called from
    // componentWillMount. Either fix it or disallow doing so completely in
    // favor of getInitialState. Alternatively, we can disallow
    // componentWillMount during server-side rendering.
    enqueueUpdate(internalInstance);
}, 

 可以注意到两个函数开始都调用了getInternalInstanceReadyForUpdate(获得内部实例用来准备更新)这个函数,那就先来看一下该函数的定义:

function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
  var internalInstance = ReactInstanceMap.get(publicInstance);
  if (!internalInstance) {
    if (__DEV__) {
      // Only warn when we have a callerName. Otherwise we should be silent.
      // We're probably calling from enqueueCallback. We don't want to warn
      // there because we already warned for the corresponding lifecycle method.
      warning(
        !callerName,
        '%s(...): Can only update a mounted or mounting component. ' +
        'This usually means you called %s() on an unmounted component. ' +
        'This is a no-op. Please check the code for the %s component.',
        callerName,
        callerName,
        publicInstance.constructor.displayName
      );
    }
    return null;
  }

  if (__DEV__) {
    warning(
      ReactCurrentOwner.current == null,
      '%s(...): Cannot update during an existing state transition (such as ' +
      'within `render` or another component\'s constructor). Render methods ' +
      'should be a pure function of props and state; constructor ' +
      'side-effects are an anti-pattern, but can be moved to ' +
      '`componentWillMount`.',
      callerName
    );
  }

  return internalInstance;
}

  

该函数刚开始就先把我们的ReactComponent实例App组件传入了一个ReactInstanceMap(React实例map)对象的get方法中,那我们再来看一下这个对象的定义:

// TODO: Replace this with ES6: var ReactInstanceMap = new Map();

var ReactInstanceMap = { /** * This API should be called `delete` but we'd have to make sure to always * transform these to strings for IE support. When this transform is fully * supported we can rename it. */ remove: function(key) { key._reactInternalInstance = undefined; }, get: function(key) { return key._reactInternalInstance; }, has: function(key) { return key._reactInternalInstance !== undefined; }, set: function(key, value) { key._reactInternalInstance = value; }, }; module.exports = ReactInstanceMap;

是不是已经有点晕了,这里我虽然不大清楚这个对象是啥,但是从它的四个方法(增删改查)能看出,应该是用来存储某些对象,用key/value的形式,不过其实文档注释里有提到了,其实该数据结构类似于es6的Map,这个我们熟悉的,就是键值对的集合,而在getInternalInstanceReadyForUpdate中直接是用了ReactInstanceMap.get(publicInstance),说明在setState之前应该是已经执行过了ReactInstanceMap.set(publiceInstance),我们再猜想一下,这里既然是get的是ReactComponent这个key,那么可能在组件mount的时候将它set进了这个ReactInstanceMap对象中去了,是否是这样呢,这个后面探讨React生命周期的一些源码的时候再去讨论,此时我们暂时不管那些,我还是把它断点出来看看这个取出的实例对象是什么:

可以看到这是一个ReactCompositeComponentWrapper,这是个啥玩意儿,不太清楚,先不管它,只要知道这个internalInstance是从这个ReactInstanceMap对象中取出来的就行了,而传入的key就是我们当前的实例组件App。

 

回到我们的enqueueSetState函数,第一步就是取出这个internalInstance,如果不存在,就返回,如果存在,就var queue = internalInstance对象上的一个_pendingStateQueue(等待状态队列)属性,如果该属性不存在,就新建一个该属性为空数组,因此可以看出_pendingStateQueue就是一个数组对象,其实再看一下上图可以看到刚开始的_pendingStateQueue是一个null。然后将我们传进去的partialState推进数组,然后执行enqueueUpdate方法,并传入internalInstance对象。这个_pendingStateQueue是什么,这里暂时先不管它,从字面意思可以看出应该是一个等待的状态队列,我们把要改的paritialState塞入了这个等待队列中。

接下来看enqueueUpdate方法:

(看到这边相信第一次看的人已经头大了,但是接下来才是真正的重头戏!坚持!)

/**
 * Mark a component as needing a rerender, adding an optional callback to a
 * list of functions which will be executed once the rerender occurs.
 */
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 setProps, 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);
}

 这个函数关键点在于batchingStrategy(批量策略)对象的isBatchingUpdates是否为true,根据字面意思就是当前并不在批量更新的时候,那么就进行批量更新,我们先来看一下这个batchingStrategy是什么:

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

module.exports = ReactDefaultBatchingStrategy;

可以看到默认情况下这个isBatchingUpdates是false,表示不在批量更新,来看一下这个batchedUpdates方法,先获取此时isBatchingUpdates的状态,然后将isBatchingUpdates改为true,表示当前正在批量更新,如果刚刚获取的状态为true,就会又回到刚刚的enqueueUpdate方法,然后走下面的dirtyComponents部分,退出if条件句。如果确实不处在批量更新状态下,则执行transaction.perform()方法,这边我先暂停一下,这里就可以看出React对于重渲染做的应对策略,就是先设立一个状态表示当前是否正在批量更新,如果不是,那就可以批量更新,然后更新view重渲染,而进行批量更新的时候,首先就先把这个标志状态改为了正在批量更新,导致后面如果有其他的批量更新要处理,那就不能进入,只能传到dirtyComponents里面去,就好比说上公共厕所时候门口会有一个标志有人/无人一样,一个道理,永远不可能两个人或多个人一起上同一个厕所。那么何为transaction呢?其实就在同一个文件的上面就已经定义了,来看:

var ReactUpdates = require('ReactUpdates');
var Transaction = require('Transaction');

var emptyFunction = require('emptyFunction');

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

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();

可以发现这里引入了一个Transaction库,也是一个非常重要的概念,那我们先来看一下这个库,然后再回过头去看这个transaction实例是什么:

/**
 * Copyright 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule Transaction
 */

'use strict';

var invariant = require('invariant');

/**
 * `Transaction` creates a black box that is able to wrap any method such that
 * certain invariants are maintained before and after the method is invoked
 * (Even if an exception is thrown while invoking the wrapped method). Whoever
 * instantiates a transaction can provide enforcers of the invariants at
 * creation time. The `Transaction` class itself will supply one additional
 * automatic invariant for you - the invariant that any transaction instance
 * should not be run while it is already being run. You would typically create a
 * single instance of a `Transaction` for reuse multiple times, that potentially
 * is used to wrap several different methods. Wrappers are extremely simple -
 * they only require implementing two methods.
 *
 * <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>
 *
 * Use cases:
 * - Preserving the input selection ranges before/after reconciliation.
 *   Restoring selection even in the event of an unexpected error.
 * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
 *   while guaranteeing that afterwards, the event system is reactivated.
 * - Flushing a queue of collected DOM mutations to the main UI thread after a
 *   reconciliation takes place in a worker thread.
 * - Invoking any collected `componentDidUpdate` callbacks after rendering new
 *   content.
 * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
 *   to preserve the `scrollTop` (an automatic scroll aware DOM).
 * - (Future use case): Layout calculations before and after DOM updates.
 *
 * Transactional plugin API:
 * - A module that has an `initialize` method that returns any precomputation.
 * - and a `close` method that accepts the precomputation. `close` is invoked
 *   when the wrapped process is completed, or has failed.
 *
 * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
 * that implement `initialize` and `close`.
 * @return {Transaction} Single transaction for reuse in thread.
 *
 * @class Transaction
 */
var Mixin = {
  /**
   * Sets up this instance so that it is prepared for collecting metrics. Does
   * so such that this setup method may be used on an instance that is already
   * initialized, in a way that does not consume additional memory upon reuse.
   * That can be useful if you decide to make your subclass of this mixin a
   * "PooledClass".
   */
  reinitializeTransaction: function() {
    this.transactionWrappers = this.getTransactionWrappers();
    if (this.wrapperInitData) {
      this.wrapperInitData.length = 0;
    } else {
      this.wrapperInitData = [];
    }
    this._isInTransaction = false;
  },

  _isInTransaction: false,

  /**
   * @abstract
   * @return {Array<TransactionWrapper>} Array of transaction wrappers.
   */
  getTransactionWrappers: null,

  isInTransaction: function() {
    return !!this._isInTransaction;
  },

  /**
   * Executes the function within a safety window. Use this for the top level
   * methods that result in large amounts of computation/mutations that would
   * need to be safety checked. The optional arguments helps prevent the need
   * to bind in many cases.
   *
   * @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(method, scope, a, b, c, d, e, f) {
    invariant(
      !this.isInTransaction(),
      'Transaction.perform(...): Cannot initialize a transaction when there ' +
      'is already an outstanding transaction.'
    );
    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;
  },

  initializeAll: function(startIndex) {
    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] = Transaction.OBSERVED_ERROR;
        this.wrapperInitData[i] = wrapper.initialize ?
          wrapper.initialize.call(this) :
          null;
      } finally {
        if (this.wrapperInitData[i] === Transaction.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) {
          }
        }
      }
    }
  },

  /**
   * Invokes each of `this.transactionWrappers.close[i]` functions, passing into
   * them the respective return values of `this.transactionWrappers.init[i]`
   * (`close`rs that correspond to initializers that failed will not be
   * invoked).
   */
  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 {
        // 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 !== Transaction.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;
  },
};

var Transaction = {

  Mixin: Mixin,

  /**
   * Token to look for to determine if an error occurred.
   */
  OBSERVED_ERROR: {},

};

module.exports = Transaction;  

不要看这段代码这么长,实际上很多都是注释,别被吓到了-。-!注释开头给了个图,大概解释一下执行perform的流程:perform这个方法接收一些method参数,然后有一些wrappers出现了,wrappers的initialize开始执行,执行完后,method开始执行,然后wrappers的close开始执行,执行完后结束流程,有几个要注意,wrappers是在创建的时候就已经注入了,并且根据wrapper的顺序,先执行initialize的先执行close。说到这差不多能大概明白这个库的perform的作用了:给传入的method执行前后添加一些钩子函数。method还是照常执行,只不过在执行的前后会先执行一些别的函数,具体流程如下:

首先reinitializeTransaction方法主要功能是清空wrapperInitData,可理解为初始化钩子函数,并且通过getTransactionWrappers这个方法来获取到新的wrapper,所以我们如果要给transaction实例添加wrapper的时候就要通过getTransactionWrappers这个方法来传值,那么如何遍历传进去的wrappers呢?initializeAll和closeAll这两个方法就是遍历所有wrappers的initialize和close方法,源码中可以看到,都是通过var transactionWrappers = this.transactionWrappers来拿到wrappers的,而这个this.transactionWrappers就是刚刚在初始化时候通过getTransactionWrappers方法来获取的,this.transactionWrappers = this.getTransactionWrappers();在perform方法源码中可以看到先执行了initializeAll,然后执行了method,最后执行了closeAll。最后将整个过程赋给这个Transaction对象的Mixin属性,再将其导出,这就是完整的Transaction库。
那么回到刚刚的transaction实例,它是由ReactDefaultBatchingStrategyTransaction这个构造函数创建的,我们通过es6的Object.assign在该构造函数的原型上添加了一些东西:Transaction.Mixin,还有我们自定义了一个对象,里面有一个同名的方法getTransactionWrappers,用来覆盖掉Transaction库中的同名方法,return的TRANSACTION_WRAPPERS是我们定义的两个wrappers,一个是FLUSH_BATCHED_UPDATES,还有一个是RESET_BATCHED_UPDATES,两个的initialize都是emptyFunction,这个库基本没什么用,就是生成一个空的function或者闭包,重点在close方法上,RESET_BATCHED_UPDATES的close方法比较简单,就一句话,把批量更新状态改为false,表示当前已经不在批量更新,而FLUSH_BATCHED_UPDATES就比较重要了,它的close是ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),激动么,终于看到update这个词了,我们再来看一下ReactUpdates.js文件中对于flushBatchedUpdates这个方法的定义:
var flushBatchedUpdates = function() {
  // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
  // array and perform any updates enqueued by mount-ready handlers (i.e.,
  // componentDidUpdate) but we need to check here too in order to catch
  // updates enqueued by setState callbacks and asap calls.
  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);
    }
  }
}; 

我们发现执行一个while循环,循环通过transaction.perform来执行runBatchedUpdates,这个transaction实例的钩子函数有哪些,这个我没有深入去看(其实后来还是去看了一下,确实是清空了dirtyComponents),因为我看到了上面注释第一行提到了它的wrappers的功能,是清空这个dirtyComponents,这个dirtyComponents是不是有点眼熟,就是我们刚刚在enqueueUpdate那个函数中,当此时是批量更新时,我们会将传入的component(也就是那个internalInstance,即ReactCompositeComponentWrapper)推入dirtyComponents中去,这个dirtyComponents是什么,暂时我们只要知道它是一个数组就行了,继续刚刚的,我们来看一下这个runBatchedUpdates函数的定义:

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  invariant(
    len === dirtyComponents.length,
    'Expected flush transaction\'s stored dirty-components length (%s) to ' +
    'match dirty-components array length (%s).',
    len,
    dirtyComponents.length
  );

  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.
  dirtyComponents.sort(mountOrderComparator);

  for (var i = 0; i < len; i++) {
    // If a component is unmounted before pending changes apply, it will still
    // be here, but we assume that it has cleared its _pendingCallbacks and
    // that performUpdateIfNecessary is a noop.
    var component = dirtyComponents[i];

    // If performUpdateIfNecessary happens to enqueue any new updates, we
    // shouldn't execute the callbacks until the next render happens, so
    // stash the callbacks first
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      // Duck type TopLevelWrapper. This is probably always true.
      if (
        component._currentElement.props ===
        component._renderedComponent._currentElement
      ) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }

    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction
    );

    if (markerName) {
      console.timeEnd(markerName);
    }

    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance()
        );
      }
    }
  }
}

 这段源码有点复杂,我来把关键点提出来看一下:

function runBatchedUpdates(transaction) {

  var len = transaction.dirtyComponentsLength;

  for (var i = 0; i < len; i++) {

      var component = dirtyComponents[i];
      ReactReconciler.performUpdateIfNecessary(
          component,
          transaction.reconcileTransaction
      );

  }

}

是不是简单许多?我们对dirtyComponents进行了一个循环,并且把里面每一个组件都传给了一个ReactReconciler.performUpdateIfNecessary(如果有必要的话执行更新)这个方法,那我们再来看一下这个方法的定义,注意,这里是ReactReconciler(React调和器)这个对象中的方法,不要搞错了,因为React整个项目中同名的方法有很多个,但是所属对象不同:

/**
   * Flush any dirty changes in a component.
   *
   * @param {ReactComponent} internalInstance
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
  performUpdateIfNecessary: function(
    internalInstance,
    transaction
  ) {
    internalInstance.performUpdateIfNecessary(transaction);
    if (__DEV__) {
      ReactInstrumentation.debugTool.onUpdateComponent(internalInstance);
    }
  },

这边又出现了一个internalInstance参数,然后它也有一个同名的performUpdateIfNecessary方法,但是我们不知道这个方法是谁的方法,那我们看一下刚刚传入的是什么,就是那个dirtyComponents组件数组,也就是由那些ReactCompositeComponentWrapper组成的,直接来看ReactCompositeComponent.js中的源码:

performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(
        this,
        this._pendingElement,
        transaction,
        this._context
      );
    }

    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context
      );
    }
 },  

receiveComponent最终会触发updateComponent,而updateComponent会刷新view,最终完成更新!

以上就是整个React内部setState的完整流程,那其实关于最初列举的几个例子,关键点就在于当前状态是否为批量更新,打印出来为0的,说明当前正处在批量更新,所以组件被推入进了dirtyComponents,但是当此时的批量更新结束时,Transaction的钩子函数close会把isBatchingUpdate改为false,所以当下一次setState时,会把之前推入dirtyComponents的所有组件重新遍历一遍,然后执行更新。至于为什么不能在getInitialState,componentWillMount, render,componentWillUpdate中使用setState,是因为mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了。

 

以上。

 

 

==============

2018.01.18上午更新:

今天我又发现一个奇怪的现象,看代码:

constructor(){
  super();
  this.state = {
     index: 0
  }  
}

handleClick(){
    this.setState({
        index: this.state.index + 1
    });
    console.log(this.state.index)
}


<button onClick={this.handleClick}>{this.state.index}</button>

点击一下,打印出来什么?为什么还是0?!那就说明isBatchingUpdate又变成true了,到底谁在里面!!! 但是button里的text已经是1了啊,到底是谁把它更新了?行,那就断点调试一下:

果然是true,再看右边调用栈,果然有人用了batchUpdates,来看看是谁,一个叫dispatchEvent的家伙,我们来看看这是啥:

那就应该是事件里面的内容了,具体细节不大清楚,那可以知道应该是这个dispatchEvent先执行了batchUpdates,然后把isbatchingUpdate改为true,所以此时的事件点击没有能直接去更新,而是进入了dirtyComponent,而事件结束的时候,dispatchEvent的transaction的closeAll又遍历了dirtyComponent,所以执行更新,也就是说,整个状态遍历更新都是交给这个dispatchEvent来完成的,而并非由我们的事件直接操作,至于里面的细节,下回再讲!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1

posted on 2018-01-17 10:24  言先生  阅读(496)  评论(0编辑  收藏  举报