setState源码
ReactComponent.prototype.setState = function (partialState, callback) {this.updater.enqueueSetState(this, partialState);if (callback) {this.updater.enqueueCallback(this, callback, 'setState');}};
入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去。这里我们以对象形式的入参为例,可以看到它直接调用了 this.updater.enqueueSetState 这个方法:
enqueueSetState: function (publicInstance, partialState) {// 根据 this 拿到对应的组件实例var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');// 这个 queue 对应的就是一个组件实例的 state 数组var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);queue.push(partialState);// enqueueUpdate 用来处理当前的组件实例enqueueUpdate(internalInstance);}
enqueueSetState 做了两件事:
- 将新的 state 放进组件的状态队列里;
 - 用 enqueueUpdate 来处理将要更新的实例对象。
 
继续往下走,看看 enqueueUpdate 做了什么:
function enqueueUpdate(component) {ensureInjected();// 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段if (!batchingStrategy.isBatchingUpdates) {// 若当前没有处于批量创建/更新组件的阶段,则立即更新组件batchingStrategy.batchedUpdates(enqueueUpdate, component);return;}// 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”dirtyComponents.push(component);if (component._updateBatchNumber == null) {component._updateBatchNumber = updateBatchNumber + 1;}}
这个 enqueueUpdate 非常有嚼头,它引出了一个关键的对象——batchingStrategy,该对象所具备的isBatchingUpdates属性直接决定了当下是要走更新流程,还是应该排队等待;其中的batchedUpdates 方法更是能够直接发起更新流程。由此我们可以大胆推测,batchingStrategy 或许正是 React 内部专门用于管控批量更新的对象。
接下来,我们就一起来研究研究这个 batchingStrategy。
/*** batchingStrategy源码**/var ReactDefaultBatchingStrategy = {// 全局唯一的锁标识isBatchingUpdates: false,// 发起更新动作的方法batchedUpdates: function(callback, a, b, c, d, e) {// 缓存锁变量var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates// 把锁“锁上”ReactDefaultBatchingStrategy. isBatchingUpdates = trueif (alreadyBatchingStrategy) {callback(a, b, c, d, e)} else {// 启动事务,将 callback 放进事务里执行transaction.perform(callback, null, a, b, c, d, e)}}}
batchingStrategy 对象并不复杂,你可以理解为它是一个“锁管理器”。
这里的“锁”,是指 React 全局唯一的 isBatchingUpdates 变量,isBatchingUpdates 的初始值是 false,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate 去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents 里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。
理解了批量更新整体的管理机制,还需要注意 batchedUpdates 中,有一个引人注目的调用:
transaction.perform(callback, null, a, b, c, d, e)
Transaction 就像是一个“壳子”,它首先会将目标函数用 wrapper(一组 initialize 及 close 方法称为一个 wrapper) 封装起来,同时需要使用 Transaction 类暴露的 perform 方法去执行它。如上面的注释所示,在 anyMethod 执行之前,perform 会先执行所有 wrapper 的 initialize 方法,执行完后,再执行所有 wrapper 的 close 方法。这就是 React 中的事务机制。
流程图

- partialState:setState传入的第一个参数,对象或函数
 - _pendingStateQueue:当前组件等待执行更新的state队列
 - isBatchingUpdates:react用于标识当前是否处于批量更新状态,所有组件公用
 - dirtyComponent:当前所有处于待更新状态的组件队列
 - transcation:react的事务机制,在被事务调用的方法外包装n个waper对象,并一次执行:waper.init、被调用方法、waper.close
 FLUSH_BATCHED_UPDATES:用于执行更新的waper,只有一个close方法
执行步骤
对照上面流程图的文字说明,大概可分为以下几步:
1.将setState传入的partialState参数存储在当前组件实例的state暂存队列中。
- 2.判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列中。
 - 3.如果未处于批量更新状态,将批量更新状态标识设置为true,用事务再次调用前一步方法,保证当前组件加入到了待更新组件队列中。
 - 4.调用事务的waper方法,遍历待更新组件队列依次执行更新。
 - 5.执行生命周期componentWillReceiveProps。
 - 6.将组件的state暂存队列中的state进行合并,获得最终要更新的state对象,并将队列置为空。
 - 7.执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。
 - 8.执行生命周期componentWillUpdate。
 - 9.执行真正的更新,render。
 - 10.执行生命周期componentDidUpdate。
 
到这里,相信你对 isBatchingUpdates 管控下的批量更新机制已经了然于胸。但是 setState 为何会表现同步这个问题,似乎还是没有从当前展示出来的源码里得到根本上的回答。这是因为 batchedUpdates 这个方法,不仅仅会在 setState 之后才被调用。若我们在 React 源码中全局搜索 batchedUpdates,会发现调用它的地方很多,但与更新流有关的只有这两个地方:
// ReactMount.js_renderNewRootComponent: function( nextElement, container, shouldReuseMarkup, context ) {// 实例化组件var componentInstance = instantiateReactComponent(nextElement);// 初始渲染直接调用 batchedUpdates 进行同步渲染ReactUpdates.batchedUpdates(batchedMountComponentIntoNode,componentInstance,container,shouldReuseMarkup,context);...}
这段代码是在首次渲染组件时会执行的一个方法,我们看到它内部调用了一次 batchedUpdates,这是因为在组件的渲染过程中,会按照顺序调用各个生命周期函数。开发者很有可能在声明周期函数中调用 setState。因此,我们需要通过开启 batch 来确保所有的更新都能够进入 dirtyComponents 里去,进而确保初始渲染流程中所有的 setState 都是生效的。
下面代码是 React 事件系统的一部分。当我们在组件上绑定了事件之后,事件中也有可能会触发 setState。为了确保每一次 setState 都有效,React 同样会在此处手动开启批量更新。
// ReactEventListener.jsdispatchEvent: function (topLevelType, nativeEvent) {...try {// 处理事件ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);} finally {TopLevelCallbackBookKeeping.release(bookKeeping);}}
话说到这里,一切都变得明朗了起来:isBatchingUpdates 这个变量,在 React 的生命周期函数以及合成事件执行前,已经被 React 悄悄修改为了 true,这时我们所做的 setState 操作自然不会立即生效。当函数执行完毕后,事务的 close 方法会再把 isBatchingUpdates 改为 false。
以 increment 方法为例,整个过程像是这样:
increment = () => {// 进来先锁上isBatchingUpdates = trueconsole.log('increment setState前的count', this.state.count)this.setState({count: this.state.count + 1});console.log('increment setState后的count', this.state.count)// 执行完函数再放开isBatchingUpdates = false}
很明显,在 isBatchingUpdates 的约束下,setState 只能是异步的。而当 setTimeout 从中作祟时,事情就会发生一点点变化:
reduce = () => {// 进来先锁上isBatchingUpdates = truesetTimeout(() => {console.log('reduce setState前的count', this.state.count)this.setState({count: this.state.count - 1});console.log('reduce setState后的count', this.state.count)},0);// 执行完函数再放开isBatchingUpdates = false}
会发现, isBatchingUpdates对 setTimeout 内部的执行逻辑完全没有约束力。因为 isBatchingUpdates 是在同步代码中变化的,而 setTimeout 的逻辑是异步执行的。当 this.setState 调用真正发生的时候,isBatchingUpdates 早已经被重置为了 false,这就使得当前场景下的 setState 具备了立刻发起同步更新的能力。所以咱们前面说的没错——setState 并不是具备同步这种特性,只是在特定的情境下,它会从 React 的异步管控中“逃脱”掉。
