美文网首页
react源码6 - setState批量更新batchedUp

react源码6 - setState批量更新batchedUp

作者: 邵志远 | 来源:发表于2020-06-06 16:19 被阅读0次

    很多人在面试的时候遇到过这样一道题,

     state = {
        count: 0,
      };
      handleClick = () => {
        this.count();
      };
      count = () => {
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
      };
    
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>{this.state.count}</button>
          </div>
        );
      }
    

    问:这几个console分别是什么?
    很多人都会说setState会合并,所以三个都是0。可能再加个异步的说法。然而我们再看下面这道题。

    state = {
        count: 0,
      };
      handleClick = () => {
        setTimeout(() => {
          this.count();
        },0)
      };
      count = () => {
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
        this.setState({count: this.state.count + 1});
        console.log(this.state.count);
      };
    
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>{this.state.count}</button>
          </div>
        );
      }
    

    this.count外面包了一层setTimeout,那么现在的结果是什么呢?
    很出乎意料,加了setTimeout之后结果就变成了

    image.png
    好像之前的异步合并都不存在了,这是为什么呢。
    function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
      addRootToSchedule(root, expirationTime);
      if (isRendering) {
        // Prevent reentrancy. Remaining work will be scheduled at the end of
        // the currently rendering batch.
        return;
      }
    
      if (isBatchingUpdates) {
        // Flush work at the end of the batch.
        if (isUnbatchingUpdates) {
          // ...unless we're inside unbatchedUpdates, in which case we should
          // flush it now.
          nextFlushedRoot = root;
          nextFlushedExpirationTime = Sync;
          performWorkOnRoot(root, Sync, true);
        }
        return;
      }
    
      // TODO: Get rid of Sync and use current time?
      if (expirationTime === Sync) {
        performSyncWork();
      } else {
        scheduleCallbackWithExpirationTime(root, expirationTime);
      }
    }
    

    react里的更新都要经过requestWork,执行setState的时候通过debug可以看到三次setState过程isBatchingUpdates都为true,也就是都会return,并不会执行react的调度更新performSyncWork。那为什么会这样呢,我们往前看执行过程,可以找到一个batchedUpdates的方法。

    function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
      const previousIsBatchingUpdates = isBatchingUpdates;
      isBatchingUpdates = true;
      try {
        return fn(a);
      } finally {
        isBatchingUpdates = previousIsBatchingUpdates;
        if (!isBatchingUpdates && !isRendering) {
          performSyncWork();
        }
      }
    }
    

    可以看到,它会return执行一个函数,这个函数相当于我们上面的handleClick(当然还会经过一系列的事件绑定),然后它会判断!isBatchingUpdates && !isRendering如果正确的话就执行performSyncWork()也就是react的指挥调度更新,正常情况下,它会等三个updates都创建完之后,再触发调度更新。但是在setTimeout的上下文它的执行环境是window,并没有isBatchingUpdates设置true的情况,所以它就会立即执行更新。
    那我们想在setTimeout里执行函数函数,也不想多次触发更新该怎么办呢,在react-dom里引用batchedUpdates方法,并使用它就ok了。

     setTimeout(() => {
          batchedUpdated(() => this.count());
     },0)
    

    通过上面的过程,我们可以得到一个结论:

    setsetState它本身的方法调用是同步的,但是setState并不标志着react的state立马就更新了,这个更新是要根据我们当前环境的执行上下文来判定的,如果处于批量更新的情况下,那么不会立马更新,反之则有可能会立即更新。(有可能是因为这里还有concurrentMode等异步渲染的情况)

    相关文章

      网友评论

          本文标题:react源码6 - setState批量更新batchedUp

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