React setState机制

590 阅读3分钟
  1. React 是利用更新队列 this._pendingStateQueue 以及更新状态 this._pendingReplaceStatethis._pendingForceUpdate 来实现 setState 的异步更新机制的。

  2. 在声明周期中,直接传递对象的 setState 会被合并成一次;使用函数传递 state 不会被合并。

    // this.state.index 初始值为 1
    // 使用对象传递 state
    componentDidMount() {
        this.setState({ index: this.state.index + 1 }, () => {
            console.log(this.state.index);  // 2
        })
        this.setState({ index: this.state.index + 1 }, () => {
            console.log(this.state.index); // 2
        })
        
        setTimeout(()=> {
          // setTimeout 使用对象或函数传递 state 结果一样
          this.setState((prevState) => ({ index: prevState.index + 1 }), () => {
              console.log(this.state.index);  // 3
          })
          this.setState((prevState) => ({ index: prevState.index + 1 }), () => {
              console.log(this.state.index);  // 4
          })
        }, 0)
    }
    
    // 使用函数传递 state
    componentDidMount() {
        this.setState((prevState) => ({ index: prevState.index + 1 }), () => {
          console.log(this.state.index);  // 3
        })
        this.setState((prevState) => ({ index: prevState.index + 1 }), () => {
          console.log(this.state.index);  // 3
        })
    
        setTimeout(()=> {
          this.setState((prevState) => ({ index: prevState.index + 1 }), () => {
            console.log(this.state.index);  // 4
          })
          this.setState((prevState) => ({ index: prevState.index + 1 }), () => {
            console.log(this.state.index);  // 5
          })
        }, 0)
    }
    
  3. setState 通过一个队列机制实现 state 更新。当执行 setState 时,会将需要更新的 state 合并后放入状态队列,而不会立即更新 this.state,队列机制可以高效地批量更新 state。

  4. 调用 setState 方法时实际执行了什么操作?

    • 实际上会执行 enqueueSetState 方法,并对 partialState 以及 _pendingStateQueue 更新队列进行合并操作,最终通过 enqueueUpdate 方法执行 state 更新。
    • 而 performUpdateIfNecessary 方法会获取 _pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 receiveComponent 和 updateComponent 方法进行组件更新。
  5. enqueueUpdate 是如何更新 state 的?

    先根据 batchingStrategy.isBatchingUpdates 的值判断当前是否处于批量更新模式:

    • isBatchingUpdates 为 false,表示当前不处于批量更新模式,对所有队列中的更新执行 batchingStrategy.batchedUpdates 方法:遍历 dirtyComponents,调用 updateComponent 方法,更新 pending state 或 props。
    • isBatchingUpdates 为 false, 表示当前处于批量更新模式,只将组件保存到 dirtyComponents 中。
    function enqueueUpdate(component) {
        ensureInjected();
        
        // 如果不处于批量更新模式
        if (!batchingStrategy.isBatchingUpdates) {
            batchingStrategy.batchedUpdates(enqueueUpdate, component);
            return;
        }
        // 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中
        dirtyComponents.push(component);
    }
    

    batchingStrategy 是一个对象,定义了一个 isBatchingUpdates 的布尔值,和 batchedUpdates方法。

    var ReactDefaultBatchingStrategy = {
        isBatchingUpdates: false,
        
        batchedUpdates: function(callback, a, b, c, d, e) {
            var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
            ReactDefaultBatchingStrategy.isBatchingUpdates = true;
            
            if (alreadyBatchingUpdates) {
            /**
            这一部分应该是因为在React 15.0之前的版本中,为开发者提供了batchedUpdates方法,所以可能会在批量更新模式中走入这部分逻辑。
            后来移除了这个API后,应该不会在批量更新模式下走入这部分逻辑了吧。(待确认)
            **/
                callback(a, b, c, d, e);
            } else {
                transaction.perform(callback, null, a, b, c, d, e);
            }
        }
    }
    
  6. 什么是事务?

    在batchedUpdates 方法中,调用了 transaction.perform,这个 transaction 即是事务。

    事务是将需要执行的方法使用 wrapper 封装起来,再通过事务提供的 perform 方法执行。而在 perform 之前,先执行所有 wrapper 中的 initialize 方法,执行完 perform 后,再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper。事务支持多个 wrapper 叠加。

    在close 方法中,会将 isBatchingUpdates 的值置为 false。

  7. 如何实现自己需要的事务?

    把事务提供的 mixin 方法混入自己的事务中,另外还要实现一个抽象的 getTransactionWrappers 接口。这个接口用来获取所有需要封装的前置方法(initialize)和收尾方法(close),因此它需要返回一个由对象组成的数组,每个对象分别有 key 为 initialize 和 close 的方法。

    var Transaction = require('./Transaction');
    
    var MyTransaction = function() {
        // ...
    };
    
    Object.assign(MyTransaction.prototype, Transaction.Mixin, {
        getTransactionWrappers: function() {
            return [{
                initialize: function() {
                    console.log('before method transform');
                },
                close: function() {
                    console.log('after method transform')
                }
            }];
        }
    });
    
    var transaction = new MyTransaction();
    var testMethod = function() {
        console.log('test');
    }
    transaction.perform(testMethod);
    
    // 执行结果
    // before method transform
    // test
    // after method transform
    
  8. 在 React 的生命周期和合成事件中,React 处于批量更新机制中,这时 isBatchingUpdate 为 true。这时无论调用多少次 setState, 都不会执行更新,而是将要更新的 state 存入 _pendingStateQueue,将要更新的组件存入 dirtyComponent。

  9. 当上一次更新机制执行完毕,isBatchingUpdate 会被置为 false,这时将执行之前累积的setState。


参考:

  1. 深入 React 技术栈,陈屹。
  2. 【掘金】【React深入】setState的执行机制: juejin.cn/post/684490…