关于setState是异步还是同步

setState

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout、setInterval 中都是同步的。
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新。在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

原理

当调用setState时,会调用一个enqueueSetState方法,这个方法会将this、要更新的state以及callback分别放入state队列和回调队列中,随后调用enqueueUpdate方法

// src/isomorphic/modern/class/ReactComponent.js
ReactComponent.prototype.setState = function(partialState, callback) {
  // ...
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback);
  }
};
// src/renderers/shared/reconciler/ReactUpdateQueue.js
enqueueSetState: function(publicInstance, partialState) {
  // 获取 ReactComponent 组件对象(这里的组件对象指的是调用了this.setState的组件)
  var internalInstance = getInternalInstanceReadyForUpdate(
    publicInstance,
    'setState'
  );

  if (!internalInstance) {
    return;
  }
  // 将 partialState 放入组件的状态队列
  var queue =
    internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  queue.push(partialState);

  enqueueUpdate(internalInstance);
},

enqueueUpdate方法则判断当前组件是否处于创建或者更新组件阶段,如果是,则将需要更新的组件放在dirtyComponents数组里,否则开始处理各个组件的update事务

function enqueueUpdate(component) {
 // ...

// 如果不是正处于创建或更新组件阶段,则处理 update 事务
 if (!batchingStrategy.isBatchingUpdates) {
   batchingStrategy.batchedUpdates(enqueueUpdate, component);
   return;
 }

// 如果正在创建或更新组件,暂且先不处理 update,只是将组件放在 dirtyComponents 数组中
  dirtyComponents.push(component);
}

短总结:在执行setState的时候,React Component将newState存入了自己的等待队列,然后使用全局的批量策略对象batchingStrategy来查看当前执行流是否处在批量更新中,如果已经处于更新流中,就将记录了newState的React Component存入dirtyeComponent中,如果没有处于更新中,遍历dirty中的component,调用updateComponent,进行state或props的更新,刷新component。

总结:

  1. enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component;
  2. 如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
  3. batchedUpdates发起一次transaction.perform()事务;
  4. 开始执行事务初始化,运行,结束三个阶段;
    初始化:事务初始化阶段没有注册方法,故无方法要执行;

运行:执行setSate时传入的callback方法,一般不会传callback参数(这一块还需要理解一下);
结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法。

  1. FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。

为什么有的“异步”有的“同步”

add() {
    this.setState({
      count: this.state.count + 1
    });
    this.setState({
      count: this.state.count + 1
    });
  }
setTimeout(() => {
  this.setState({
    count: this.state.count + 1
  });
  this.setState({
    count: this.state.count + 1
  });
}, 0);

第一种情况,在执行第一个setState时,本身已经处于一个点击事件触发的这个大事务中,已经触发了一个batchedUpdates,isBatchingUpdates为true,所以两个setState都会被批量更新,这时候属于异步过程,this.state并没有立即改变,执行setState只是相当于把partialState(前面说的部分state)传入dirtyComponents,最后在事务的close阶段执行flushBatchedUpdates去重新渲染。

第二种情况,有了setTimeout,两次setState都会在点击事件触发的大事务中的批量更新batchedUpdates结束之后再执行,所以他们会触发两次批量更新batchedUpdates,也就会执行两个事务和函数flushBatchedUpdates,就相当于同步更新的过程了。复合事件应该也是同样的道理。

 Webpack性能优化
详解前端脚手架(一):脚手架的执行原理和实现原理 
上一篇:Webpack性能优化
下一篇:详解前端脚手架(一):脚手架的执行原理和实现原理


如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信
QQ