从源码理解setState(React15.6.0)

665 阅读17分钟

本文基于React15.6.0介绍setState机制,同时包括常见的setState问题比如setState的批量更新,同步异步等。希望各位能有所收获。

1,React中的事务(transaction)

介绍setState机制之前,我们先了解一下React中的 事务(transaction) (别怕,简单)

事务(transaction):关于react中的事务,本质就是将 目标方法 进行包装,目标方法 执行前先执行所有包装的initialize方法,之后执行 目标方法 ,最后执行行所有包装的close方法。

通过图直观的感受事务(transaction)的运作方式

image.png

通过代码直观的感受事务(transaction)的运作方式

class Transaction {
    constructor() {
        this.transactionWrappers = []
    }
    // 添加包装方法[{init1,close1},{init2,close2},......]
    setTransactionWrappers(transactionWrappers) {
        this.transactionWrappers = transactionWrappers
    }
    // 执行事务
    perform(method) {
        // 首先执行所有包装方法中的init
        this.transactionWrappers.forEach(({ initialize, close }) => initialize())
        // 执行当前目标方法
        method()
        // 首先执行所有包装方法中的close
        this.transactionWrappers.forEach(({ initialize, close }) => close())
    }
}

const wrapper1 = {
    initialize: () => console.log('wrapper1-initialize'),
    close: () => console.log('wrapper1-close')
}

const wrapper2 = {
    initialize: () => console.log('wrapper2-initialize'),
    close: () => console.log('wrapper2-close')
}
// 目标方法
const showCurrentTime = () => console.log(new Date().toGMTString())

const trasaction = new Transaction()
trasaction.setTransactionWrappers([wrapper1, wrapper2])
trasaction.perform(showCurrentTime)

// 输出:
// wrapper1-initialize
// wrapper2-initialize
// Tue, 31 Aug 2021 09:08:12 GMT
// wrapper1-close
// wrapper2-close

通过 react源码中提供的事务流程图 的运作方式

image.png

相信看到这里已经理解react中的事务到底是如何运作,那么继续:

2,React批量更新策略事务(ReactDefaultBatchingStrategyTransaction)

现在将介绍 React批量更新策略事务 ,这个也是为我们搞清楚setState机制做铺垫(别怕,简单)

我们知道一个事务的wrapper是什么,那么我们就清楚这个事务是如何运行,所以这里我们看看 React批量更新策略事务 的wrapper方法做了什么。

 // React批量更新策略事务中的两个wrapper:
 // FLUSH_BATCHED_UPDATES
 // RESET_BATCHED_UPDATES

 // wrapper1:
 FLUSH_BATCHED_UPDATES = {
    // initialize:空函数,什么都不做
    initialize: emptyFunction,
    // close:调用flushBatchedUpdates,该函数作用是批量更新组件
    close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
 }
 // wrapper2:
 RESET_BATCHED_UPDATES = {
    // initialize:空函数,什么都不做
    initialize: emptyFunction,
    // close:将 全局 React批量更新标识(ReactDefaultBatchingStrategy.isBatchingUpdates)重置为false
    close: function () {  ReactDefaultBatchingStrategy.isBatchingUpdates = false; },
 }

flushBatchedUpdates:这个函数作用即对组件进行批量更新(虚拟Dom到真实Dom的映射过程),重要的事情再说一遍,这个函数作用即对组件进行批量更新。(这个函数暂不深入研究,主要因为我也没参透这里面具体逻辑,但这不影响我们整体流程介绍,我们需要做的就是记住,在这个函数完成组件们的批量更新行为)。

ReactDefaultBatchingStrategy.isBatchingUpdates:这个变量控制着React是否进行批量更新操作,如果是true,那么遇到更新操作就会进行批量更新,false则不进行批量更新(即发生一次更新,就立即更新)。初始值为false。

现在,我们用一张图看看 React批量更新策略事务 的流程:

image.png

了解了React批量更新策略事务,那么这个事务将用在什么地方呢,或者说,React中哪些方法会作为该事务的目标方法,然后启动React批量更新策略事务整个流程呢。让我们继续:

3,合成事件中的setState行为

前面做了这么多铺垫,现在我们就要开始正题,这里以合成事件执行流程举例。

3.1,开启批量更新(在合成事件执行之前)

下面是一个简单的React组件,我们关注点放在click事件(合成事件)中,即从点击add按钮开始说起:

import React from 'react';
import ReactDom from 'react-dom'

class Conuter extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            num: 1
        }
    }
    click = () => {
        console.log('this.state.num:', this.state.num);
        this.setState({ num: this.state.num + 1 })
        this.setState({ num: this.state.num + 2 })
        console.log('this.state.num:', this.state.num);
    }
    render() {
        return <div>
            <span>{this.state.num}</span>
            <button onClick={this.click}>add</button>
        </div>
    }
}

ReactDom.render(<Conuter />, document.getElementById('root'));

首先,点击click事件,会将React批量更新策略中的批量更新标识设置为true,即开启React批量更新,让我们在React源码中找到这个操作:

// 源码坐标:
// src/renderers/dom/client/ReactEventListener.js
// line:155

dispatchEvent: function(topLevelType, nativeEvent) {
    if (!ReactEventListener._enabled) {
      return;
    }

    var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
      topLevelType,
      nativeEvent,
    );
    try {
      // Event queue being processed in the same cycle allows
      // `preventDefault`.
      
      // 将在这里开启React批量更新
      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    } finally {
      TopLevelCallbackBookKeeping.release(bookKeeping);
    }
}

ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping):这里会开启React批量更新,即将ReactDefaultBatchingStrategy.isBatchingUpdates设置为true。

进入ReactUpdates.batchedUpdates,看看这里除了开启React批量更新还做了什么:

// 源码坐标:
// src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
// line:52

var ReactDefaultBatchingStrategy = {
  // 1,isBatchingUpdates:之前一直说的 React批量更新标识 就在这里
  isBatchingUpdates: false,
  // 2,batchedUpdates:ReactUpdates.batchedUpdates调用的就是这个batchedUpdates函数
  batchedUpdates: function(callback, a, b, c, d, e) {
    // 3,alreadyBatchingUpdates:缓存当前批量更新标识
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    // 4,将批量更新标识设置true,即之后将开启批量更新
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    // 5,alreadyBatchingUpdates即初始isBatchingUpdates,初始isBatchingUpdates为false,所以这里不会进去
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      // 6,alreadyBatchingUpdates为false,执行这里代码
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

1,首先,在代码注释4的位置,将批量更新标识isBatchingUpdates从默认值false,设为true,即开启批量更新,

2,由于之前批量跟新标识为false,所以batchedUpdates(ReactUpdates.batchedUpdates)最后会进入注释6的代码,这段代码中的transaction就是前面介绍的 React批量更新策略事务(ReactDefaultBatchingStrategyTransaction)

3,transaction.perform(callback, null, a, b, c, d, e);这段代码的含义其实就是启动React批量更新策略事务,其中callBack就是事务中的目标方法:

image.png

4,所以transaction.perform(callback, null, a, b, c, d, e)执行本质就是:

  • 1,执行callback
  • 2,执行组件批量更新
  • 3,执行批量更新标识重置为false

5,对于callback,可以简单的认为就是click事件,所以现在开始执行click函数。

3.2,执行合成事件

回顾一下click代码:

click = () => {
    console.log('this.state.num:', this.state.num); // 1
    this.setState({ num: this.state.num + 1 })      // 2
    this.setState({ num: this.state.num + 2 })      // 3
    console.log('this.state.num:', this.state.num); // 4
}

执行第一行代码,输出this.state.num初始值:1

执行第二行代码:this.setState({ num: this.state.num + 1 })

这里我们终于讲到了本文主人公setState,为了清晰弄清楚setState做了什么,让我们进入setState源码(别怕,简单):

// 源码坐标:
// src/isomorphic/modern/class/ReactBaseClasses.js
// line:60

// partialState即this.setState中第一个参数,callback即this.setState中第二个参数
ReactComponent.prototype.setState = function(partialState, callback) {
  //...
  // 1,使用updater.enqueueSetState处理设置的state
  this.updater.enqueueSetState(this, partialState);
  // 2,使用updater.enqueueCallback处理设置的callback
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

this.setState({ num: this.state.num + 1 })中的{ num: this.state.num + 1 }即源码中的partialState,我们继续进入enqueueSetState看看做了什么:

// 源码坐标:
// src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
// line:232

enqueueSetState: function(publicInstance, partialState) {
    // ...
    // 1,获取当前组件实例的内部实例
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
    // ...
    // 2,向内部实例中的_pendingStateQueue添加我们的partialState
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);
    // 3,将内部实例交给enqueueUpdate执行
    enqueueUpdate(internalInstance);
},

结合上面注释,enqueueSetState做的事简而言之:

  • 1,将 partialState 暂存入组件内部实例的队列(_pendingStateQueue)中

  • 2,将 组件内部实例 交给enqueueUpdate继续处理

  • 组件内部实例(internalInstance):这里就认为这是我们当前的组件就好了

进入enqueueUpdate看看这个函数接收到我们的组件内部实例做了什么(别怕,简单):

// 源码坐标:
// src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
// line:213

function enqueueUpdate(component) {
  // ...
  // 1,如果没有开启批量更新标识,直接对组件进行更新
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // 2,如果开启批量更新标识,则将组件存入脏组件集合中
  dirtyComponents.push(component);
  // ...
}

enqueueUpdate做的事很简单,就是将我们的组件放入脏组件集合中(因为在click事件执行之前,前面有说,批量更新标识被设置成true,所以这里走注释2的代码)

至此,this.setState({ num: this.state.num + 1 })执行完毕,可以看到并没有发生组件的更新,做的事仅仅是:

  • 1,将 partialState:{ num: this.state.num + 1 } 保存到队列(_pendingStateQueue)中

  • 2,将 当前组件(组件内部实例) 添加到脏组件(dirtyComponents)集合中

现在继续执行click事件中第三行代码:this.setState({ num: this.state.num + 2 })

click = () => {
    console.log('this.state.num:', this.state.num); // 1
    this.setState({ num: this.state.num + 1 })      // 2
    this.setState({ num: this.state.num + 2 })      // 3
    console.log('this.state.num:', this.state.num); // 4
}

同第二行代码,第三行代码执行结果也是:

  • 1,将 partialState:{ num: this.state.num + 2 } 保存到队列(_pendingStateQueue)中

  • 2,将 当前组件(组件内部实例) 添加到脏组件(dirtyComponents)集合中

最后执行第四行代码,输出this.state.num依然是初始值1,原因很简单:

上面分析了第二行第三行setState代码,执行完成之后并没有做任何组件更新操作(仅保存了将要更新的state与当前组件),所以此时访问this.state获取到的是组件初始值,或者说是组件更新前的值,所以输出this.state.num:1

至此,click事件执行完毕,此时回到React批量更新策略事务(ReactDefaultBatchingStrategyTransaction)中: image.png

此时目标方法click执行完毕,继续执行React批量更新策略事务中第一个close方法 :FLUSH_BATCHED_UPDATES.close,即开始执行组件更新(取出脏组件队列中的脏组件及对应state完成组件更新)

组件更新完毕,执行React批量更新策略事务中第二个close方法 :RESET_BATCHED_UPDATES.close,即将批量更新标识isBatchingUpdates重置为false,即关闭批量更新。

至此,点击add按钮整个流程执行完毕,我们回顾一下整个流程做了什么:

  • 1,React批量更新策略的批量更新标识isBatchingUpdates设置为true(默认值false),即开启批量更新

  • 2,将合成事件(这里即click事件)添加到React批量更新策略事务运行

  • 3,执行合成事件中代码,遇到setState代码则:

    • 3.1,将要设置的partialState添加到待定state队列(_pendingStateQueue)中,不执行state更新

    • 3.2,将当前组件添加到脏组件集合中

  • 4,合成事件执行完毕,执行到React批量更新策略事务中首个close方法(FLUSH_BATCHED_UPDATES.close),该方法即对所有脏组件进行组件更新,我们的state也将在这一步完成更新。

  • 5,组件更新完毕,执行到React批量更新策略事务中第二个close方法(RESET_BATCHED_UPDATES.close),重置批量更新标识isBatchingUpdates:false,即关闭批量更新。

4,生命周期中的setState行为

前面介绍了合成事件中的setState中的setState行为,其实对于生命周期中setState行为也是如此。让我们从挂载阶段开始说起(ReactDom.render)

React挂载阶段主要源代码:

// 源码坐标:
// src/renderers/dom/client/ReactMount.js
// line:362

_renderNewRootComponent: function(
    nextElement,
    container,
    shouldReuseMarkup,
    context,
    ) {
    // ...

    // 1,监听窗口滚动和调整大小事件。我们缓存滚动值。应用程序代码可以访问它们而不触发回流
    ReactBrowserEventEmitter.ensureScrollValueMonitoring();
    // 2,创建组件实例
    var componentInstance = instantiateReactComponent(nextElement, false);
    // 3,开始批量挂载组件(从根节点开始)
    ReactUpdates.batchedUpdates(
      batchedMountComponentIntoNode,
      componentInstance,
      container,
      shouldReuseMarkup,
      context,
    );

    // 
    var wrapperID = componentInstance._instance.rootID;
    instancesByReactRootID[wrapperID] = componentInstance;
    return componentInstance;
},
  • 1,我们可以很清晰的看到,挂载阶段,首先创建了组件树实例,注释1

  • 2,之后调用ReactUpdates.batchedUpdates即开启React批量更新事务策略,其中目标方法即batchedMountComponentIntoNode,顾名思义,批量对组件树进行挂载操作,结合前面的React批量更新事务策略具体一点来看:

image.png

通过前面合成事件执行流程,很容易看出来,挂载阶段也会开启组件批量更新,如果遇到setState也会暂存partialState以及当前组件,最后对所有脏组件进行批量更新:

  • 1,获取根组件实例

  • 2,即开启组件批量更新,React批量更新策略的批量更新标识isBatchingUpdates设置为true(默认值false)

  • 3,进行组件挂载阶段

  • 3,挂载阶段生命周期中遇到setState代码则:

    • 3.1,将要设置的partialState添加到待定state队列(_pendingStateQueue)中,不执行state更新

    • 3.2,将当前组件添加到脏组件集合中

  • 4,执行到React批量更新策略事务中首个close方法(FLUSH_BATCHED_UPDATES.close),该方法即对所有脏组件进行组件更新,我们的state也将在这一步完成更新。

  • 5,组件树完毕,执行到React批量更新策略事务中第二个close方法(RESET_BATCHED_UPDATES.close),重置批量更新标识isBatchingUpdates:false,即关闭批量更新。

所以对于挂载阶段的生命周期componentWillMount,componentDidMount中的代码全部运行在React批量更新策略事务中,所以这些生命周期中setState也会进行批量更新。

对于更新阶段的大部分生命周期(例如componentWillUpdate)是不可以进行setState操作(出现更新死循环),而卸载阶段生命周期(componentWillUnmount)进行setState无效(组件卸载,setState也将失去意义)。

不过有一个生命周期例外,即componentWillReceiveProps,这个生命周期中可以使用setState,且会进行批量跟新,这块源码没有看,不过猜测应该也是处于React批量更新策略事务中运行,所以可以进行批量更新。

5,关于setState的一些问题的回答

前面我们已经了解了合成事件及组件挂载阶段生命周期中setState的机制,现在让我们在此基础上解决一些setState常见问题

5.1,关于异步的setState

通过前面setState介绍,我们很容易知道setState并非异步,本质还是同步的,只不过它的表现形式是异步的,而且只在合成事件与挂载阶段生命周期(componentWillMount,componentDidMount)以及componentWillReceiveProps才是异步的表现形式

  • setState的异步表现形式主要是在合成事件以及挂载阶段(&componentWillReceiveProps)

  • 在这些方法运行之前会打开React批量更新策略中的批量更新标识isBatchingUpdates(默认值false,设置为true),开启批量更新,同时将这些方法添加进React批量更新策略事务中

  • 位于React批量更新策略事务中的方法,遇到setState会将setState的参数partialState添加进入对应组件的_pendingStateQueue中,同时组件加入脏组件集合

  • 当这些目标方法执行完毕,此时组件(&state)并没有发生更新,但是目标方法内同步代码已经执行完毕,所以访问this.state获取到的仍然是原来的state,即给人一种setState是异步的感觉

  • 目标方法执行完毕,会将所有脏组件从根组件开始进行更新(包括state)

  • 最后关闭React批量更新标识(isBatchingUpdates重置为fasle)

    // this.state.num 初始值 1
    // 1,isBatchingUpdates:true // 开启批量更新
    click = () => {
        console.log('this.state.num:', this.state.num); // 输出1
        this.setState({ num: this.state.num + 1 })
        console.log('this.state.num:', this.state.num); // 输出1
    }
    // 2,isBatchingUpdates:fasle // 关闭批量更新
    

5.2,关于同步的setState

同步的setState一般存在于异步事件中,比如定时器事件,dom原生事件,Promise微任务,出现这种情况的本质是这些异步事件中的setState并不处于React批量更新策略中,因此setState一次,组件rerender一次

让我们用一个合成事件例子分析同步的setState:

// this.state.num 初始值 1
click = () => {                                         // 1
    console.log('this.state.num:', this.state.num);     // 2
    this.setState({ num: this.state.num + 1 })          // 3
    console.log('this.state.num:', this.state.num);     // 4
    setTimeout(() => {                                  // 5
        console.log('this.state.num:', this.state.num); // 6
        this.setState({ num: this.state.num + 1 })      // 7
        console.log('this.state.num:', this.state.num); // 8
    },0)                                                // 9
    console.log('this.state.num:', this.state.num);     // 10
}                                                       // 11         
  • 注释1:开启React批量更新标识(isBatchingUpdates设为true),将click加入React批量更新策略事务中执行

  • 注释2:输出当前this.state.num:1

  • 注释3:将partialState:{ num: this.state.num + 1 } 保存到组件的_pendingStateQueue中,组件放入脏组件(dirtyComponent)集合中,并不发生组件更新

  • 注释4:组件不发生更新,state不发生更新,输出原this.state.num:1

  • 注释5:发现定时器任务setTimeout,交给定时器线程计时,计时结束将注册任务函数加入任务队列(事件循环中的任务队列)

    // 定时器注册任务函数
    () => {                                             // 5
        console.log('this.state.num:', this.state.num); // 6
        this.setState({ num: this.state.num + 1 })      // 7
        console.log('this.state.num:', this.state.num); // 8
    }
    
  • 注释10:组件不发生更新,state不发生更新,输出原this.state.num:1

  • 注释11:click事件执行完毕:

    • 执行React批量更新策略事务第一个close方法:FLUSH_BATCHED_UPDATES.close,完成组件更新,此时this.state.num被更新为2

    • 执行React批量更新策略事务第二个close方法:RESET_BATCHED_UPDATES.close,重置批量更新标识isBatchingUpdates为false,关闭批量更新

  • 注释5:同步代码执行完毕(此时this.state.num为2,isBatchingUpdates为false),取出任务队列中的定时器注册函数执行

  • 注释6:this.state.num在注释11位置更新为2,所以此时输出this.state.num:2

  • 注释7:此时批量更新标识(isBatchingUpdates)为false,且没有开启React批量更新策略事务,此时直接执行 this.setState({ num: this.state.num + 1 })。回顾之前setState源码分析部分:

    • 1,首先将partialState加入组件的_pendingStateQueue中,之后将组件交给enqueueUpdate处理

      // 源码坐标:
      // src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
      // line:232
      
      enqueueSetState: function(publicInstance, partialState) {
          // ...
          // 1,获取当前组件实例的内部实例
          var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
          // ...
          // 2,向内部实例中的_pendingStateQueue添加我们的partialState
          var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
          queue.push(partialState);
          // 3,将内部实例交给enqueueUpdate执行
          enqueueUpdate(internalInstance);
      },
      
    • 2,进入enqueueUpdate:

      // 源码坐标:
      // src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
      // line:213
      
      function enqueueUpdate(component) {
        // ...
        // 1,如果没有开启批量更新标识,直接对组件进行更新
        if (!batchingStrategy.isBatchingUpdates) {
          batchingStrategy.batchedUpdates(enqueueUpdate, component);
          return;
        }
        // 2,如果开启批量更新标识,则将组件存入脏组件集合中
        dirtyComponents.push(component);
        // ...
      }
      
      • 此时批量更新标识(isBatchingUpdates)为false,所以执行batchingStrategy.batchedUpdates(enqueueUpdate, component);

      • 这段代码含义即直接更新组件(&state)

    • 3,组件更新完毕,state完成更新,此时this.state.num为3

  • 注释8:this.state.num在注释7位置更新为3,所以此时输出this.state.num:3

所以最后输出结果是:

click = () => {                                         // 1
    console.log('this.state.num:', this.state.num);     // 2
    this.setState({ num: this.state.num + 1 })          // 3
    console.log('this.state.num:', this.state.num);     // 4
    setTimeout(() => {                                  // 5
        console.log('this.state.num:', this.state.num); // 6
        this.setState({ num: this.state.num + 1 })      // 7
        console.log('this.state.num:', this.state.num); // 8
    },0)                                                // 9
    console.log('this.state.num:', this.state.num);     // 10
}                                                       // 11

// 输出:
// this.state.num: 1    // from注释2
// this.state.num: 1    // from注释4
// this.state.num: 1    // from注释10
// this.state.num: 2    // from注释6
// this.state.num: 3    // from注释8

5.3 关于多次setState同一个值,只有最后一次setState有效

这中情况只出现在合成事件,挂载阶段(componentWillMount,componentDidMount),componentWillReceivePropse,这些位于React批量更新策略事务中的行为方法。对于异步中的setState则不会有这种行为,因为异步代码不再React批量更新策略事务中,setState一次组件更新一次。

出现这种现象主要是因为在一次批量更新策略事务中,目标方法中的每一次setState的partialState都会添加进入当前组件_pendingStateQueue中,而在脏组件更新阶段会遍历所有的partialState与原state进行Object.assign合并,因此,对于state对象中同名属性,最后一个partialState的该属性会覆盖之前partialState对该属性的设置,因此只有最后一次setState设置的属性才是终值

结合这一部分源代码_processPendingState分析:

// 源码坐标:
// /src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
// line:896

// _processPendingState:将setState的partialState与原state合并
_processPendingState: function(props, context) {
    // 1,inst:组件实例
    var inst = this._instance;
    // 2,queue:_pendingStateQueue
    var queue = this._pendingStateQueue;
    // ...
    // 3,nextState:这里不管replace,nextState即组件实例state对象
    var nextState = Object.assign({}, replace ? queue[0] : inst.state);
    // 4,遍历_pendingStateQueue中所有partialState,使用 Object.assign 合并到原state内
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      Object.assign( nextState,
        typeof partial === 'function'
          // 5,如果是函数,获取函数返回值作为partialState合并进原state
          ? partial.call(inst, nextState, props, context)
          // 6,如果是state(一般设置的state对象),则直接向原state合并
          : partial,
      );
    }
    // 
    return nextState;
},

在注释6位置,对于每一个partialState都与原state使用Object.assign进行合并,因此对于同名属性,只有最后一个partialState才会作为该属性终值,像下面这样:

nextState = { a: 1 }
_pendingStateQueue = [{ a: 2 }, { a: 3 }]
nextState = Object.assign(nextState, ..._pendingStateQueue)
// nextState: { a : 3 }

5.4 关于多次setState同一值,如果传入函数形式state,则每一次setState都会生效

这是因为源码_processPendingState处理函数形式的partialState,每次都会将上一次更新后的state值作为函数形式的partialState的第一个参数(如下源码注释5),我们即可在函数形式partialState中获取最新state,随后返回partialState更新后的state通过Object.assign与上一次state值进行合并,完成state更新,所以这就相当于使用函数形式的partialState对state进行迭代更新,保证每一次state都能更新成功。

// 源码坐标:
// /src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
// line:896

// _processPendingState:将setState的partialState与原state合并
_processPendingState: function(props, context) {
    // 1,inst:组件实例
    var inst = this._instance;
    // 2,queue:_pendingStateQueue
    var queue = this._pendingStateQueue;
    // ...
    // 3,nextState:这里不管replace,nextState即组件实例state对象
    var nextState = Object.assign({}, replace ? queue[0] : inst.state);
    // 4,遍历_pendingStateQueue中所有partialState,使用 Object.assign 合并到原state内
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      Object.assign( nextState,
        typeof partial === 'function'
          // 5,如果是函数,获取函数返回值作为partialState合并进原state
          ? partial.call(inst, nextState, props, context)
          // 6,如果是state(一般设置的state对象),则直接向原state合并
          : partial,
      );
    }
    // 
    return nextState;
},

6,最后

至此,setState已经介绍完毕,如果有空我会更新一张setState流程图供参阅,当然对于本文也参阅了大量中外文章以及React15源码,欢迎批评指正与交流。

感谢参考