美文网首页
React细节知识之batchStrategy引发的问题

React细节知识之batchStrategy引发的问题

作者: 殊一ONLY | 来源:发表于2017-12-10 15:23 被阅读0次

      官方文档如下解释 setState:

Sets a subset of the state. Always use this to mutate state. You should treat this.state as immutable.
There is no guarantee that this.state will be immediately updated, so accessing this.state after calling this method may return the old value.
There is no guarantee that calls to setState will run synchronously,as they may eventually be batched together. You can provide an optional callback that will be executed when the call to setState is actually completed.

      this.state是不可变的,当设置state的子集时必须要使用setState。
      setState并不能确保this.state会立即更新,在调用这个函数(setState)后可能会返回没被更新的值。
      setState也并不能保证被调用后会同步运行,因为它们最终可能会被批量处理。你可以提供一个可选择的回调函数,在setState处理完成后将被立即执行。

      针对第二段this.state不会立即更新产生的问题有下面这个实例:
场景:在onclick的回调函数中对state进行量测更新,那么量测更新的结果都是基于更新之前的旧值进行计算的。

var LikeButton = React.createClass({
        getInitialState: function() {
          return {value: 0};
        },
        handleClick: function(event) {
          this.setState({value: this.state.value + 1});
          this.setState({value: this.state.value + 2});
        },
        render: function() {
          var text = '当前值为'+this.state.value;
          return (
          <div>
            <button onClick={this.handleClick}> 点击+2
            </button>
            <p>{text}</p>
            </div>
          );
        }
      });

      ReactDOM.render(
        <LikeButton />,
        document.getElementById('example')
      );

      我们期望的结果是每点击button一次,value的值加3,但是分别在同一个回调函数的两个setState中实现的。可是实现的结果并不是我们想要的,每次value的值都是+2。那么为什么会这样呢?
      在setState是批量处理策略来进行更新的,那么更新组件updateComponent方法主要是计算prop,钩子函数,还有计算新的state。新的state是通过_processPendingState方法实现的,在ReactCompositeComponent.js找到其代码:

_processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;
    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }

    return nextState;
  }

      其中queue是批量处理中待处理的state队列,nextState的第一个元素是state的原始值,后面的元素是通过遍历queue添加的,而且对于相同的state,他们都是基于同一个原始值进行更新。因此,我们在setStete中两次更新value的值都是基于上次更新或初始化的结果,由于+2是在队列的后面,因此渲染出来的结果是0,2,4,…那么如何才能实现我们想要的效果呢?先来看一看这对nextState进行复制的这段代码:

_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);

      如果stateQueue内部的元素是函数的话,则调用该函数进行渲染,而且他接受的是nextState,也就是最新更新的state,从而可以实现我们想要的效果。因此,该实例的handleClick可以更改为:

  handleClick(){
          this.setState(()=>{value: this.state.value + 1});
          this.setState(()=>{value: this.state.value + 2});
        }

      此时,每次点击按钮会显示的值是0,3,6,9…
      还有其他方法可以实现这个效果嘛?我在这篇文章中找到了答案https://juejin.im/post/599b8f066fb9a0247637d61b,他采用超时调用的方法实现的:

handleClick() { 
setTimeout(()=>{ 
this.setState({ value: this.state.value + 1 }); 
this.setState({ value: this.state.value + 2 });
 },0)
}

      通过setTimeout函数的包装,两次setState都会在click触发的批量更新batchedUpdates结束之后执行,这两次setState会触发两次批量更新batchedUpdates,当然也会执行两个事务以及函数flushBatchedUpdates,这就相当于一个同步更新的过程,自然可以达到我们的目的,这也就解释了为什么React文档中既没有说setState是同步更新或者是异步更新,只是模糊地说到,setState并不保证同步更新。

相关文章

网友评论

      本文标题:React细节知识之batchStrategy引发的问题

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