美文网首页react源码分析
react源码阅读笔记(6)setState解密

react源码阅读笔记(6)setState解密

作者: Kaku_fe | 来源:发表于2017-11-15 20:00 被阅读61次

本文使用的是react 15.6.1的代码

在react开发的时候,当我们没有使用flux,mobx,redux等状态管理管理工具的时候,总会调用this.setState这样一个api。可是你真的懂这个api吗?见过这样一个问题

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return null;
  }
};

问上述代码打印的val分别是多少?
答案是0、0、2、3,如果没有回答对也没有关系,我们来看看调用this.setState时到底发生了什么事情

首先先来看看setState的代码(代码位于ReactBaseClass):

/*
* @param {object|function} 下一个状态,会与当前的状态进行合并
* @param {?function} 当state更新后的回调函数callback 
*/
ReactComponent.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

代码极其简单,就是调用了enqueueSetState方法,同时如果存在回调,调用enqueueCallback方法,
我们在来看看 this.updater.enqueueSetState(this, partialState)做了什么
react源码阅读笔记(2)组件的渲染我们在mountComponent发现updater是通过 transaction.getUpdateQueue()来获取,在ReactReconcileTransaction中我们发现,最后返回的update就是ReactUpdateQueue.js中的ReactUpdateQueue,所以,来看看其中enqueueSetState方法

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance); 
}

代码只有一行,继续跟进了解一下 ReactUpdates.enqueueUpdate方法

function enqueueUpdate(component) {
  ensureInjected();


  //若 batchingStrategy 不处于批量更新收集状态,则 batchedUpdates 所有队列中的更新
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // 若 batchingStrategy 处于批量更新收集状态,则把 component 推进 dirtyComponents 数组等待更新
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

看到这里,尤其是batchingStrategy.batchedUpdates是不是很眼熟,我们在React源码阅读笔记(3)batchedUpdates与Transaction中介绍过,方法会在wrapperclose中调用flushBatchedUpdates更新项目,这里不在详细介绍了。

同时,根据代码得知,如果组件处于更新态,即isBatchingUpdates = true的时候,那么是不会调用更新方法的,而是将这个组件放入dirtyComponents组件队列中等待更新,当最后执行到close时,在去更新真正的state,这也解释了开头的问题,为什么在componentDidMountsetState不能实时的更新state的值,而setTimeout不会出现这种情况,因为componentDidMount时,isBatchingUpdates为true,处于更新中,而生命周期结束后,会在RESET_BATCHED_UPDATES这个wrapper中将isBatchingUpdates重置,因此setTimeout中调用state会实时更新state值。

总结

setState设计还是很巧妙的,同样巧妙的利用了transation,来完成更新的各个操作,同时,总结以前到现在的各个阶段看,react偏向于将数据缓存到队列中,最后一次性全部更新,这样好处是最后的绘制到dom上的数据一定是最新的,大大减少了绘制次数,提升了整体性能。缺点是如果刚上手react,可能会因为对state原理不清楚,使用了脏state,从而产生了不必要的麻烦。如果真要在调用setState后使用新的state,不妨使用this.setState这个api的第二个参数,传递一个回调函数进去,当全部更新完成后,在获取最新的state进行业务处理

参考文章

React 源码剖析系列 - 解密 setState

相关文章

网友评论

    本文标题:react源码阅读笔记(6)setState解密

    本文链接:https://www.haomeiwen.com/subject/xabhvxtx.html