React -- (5) useState() 执行过程【批量更新and合并机制】

1,986 阅读3分钟
<1> transaction 事务【合成事件and生命周期函数】

在React执行合成事件或者生命周期函数时,会使用一个Transaction对象将整个执行过程包裹成一个事务。在该事务中有一个环境变量——isBatchingUpdates。【可以看作是一把锁,来控制着 setState()的执行】它是实现合成事件and生命周期函数中的setState()是批量更新的关键。

因为事务的执行流程为:

2198.png

  • 在事务中会最先设置环境变量isBatchingUpdates为true

  • 而执行 setState() 之前,又总会去判断isBatchingUpdates的值

       - (1)如果为 true ,说明此时 React 正处于批量更新过程中
       - 则不会立马执行此次state更新,而是会把其放入`_pendingStateQueue【等待执行更新的state队列】`
       - (2)如果为 false ,说明此时 React 并未进行任何批量更新操作
       - 则会先去把`isBatchingUpdates`的值设为 true,把锁给锁上。
       - 遍历待更新组件队列`dirtyComponent`依次执行更新里面的组件。
       - 对于每个组件执行生命周期,并把`_pendingStateQueue【等待执行更新的state队列】`中,存放的该组件的 state 进行合并。
       - 然后执行后续更新的生命周期!!!
    
  • 该事务执行完毕后,又会把isBatchingUpdates的值设为 false

<2> setState()的执行流程

流程图如下:

2197

大致过程为:

  • 1.将setState传入的partialState参数【setState()传入的第一个参数】存储在当前组件实例的state暂存队列_pendingStateQueue中。

  • 2.判断isBatchingUpdates的值,从而判断当前React是否处于批量更新状态,如果是,将当前组件加入待更新的组件队列dirtyComponent中。

  • 3.如果未处于批量更新状态,将isBatchingUpdates的值设置为true,进行上锁。用事务再次调用前一步方法,保证当前组件加入到了待更新组件队列中。

  • 4.调用事务的waper方法,遍历待更新组件队列dirtyComponent依次执行更新。

  • 5.执行生命周期componentWillReceiveProps

  • 6.将组件的state暂存队列_pendingStateQueue中的state进行合并,获得最终要更新的state对象,并将队列置为空。

  • 7.执行生命周期componentShouldUpdate,根据返回值判断是否要继续更新。

  • 8.执行生命周期componentWillUpdate

  • 9.执行真正的更新,render

  • 10.执行生命周期componentDidUpdate

<3> 批量更新总结

上面两点很好的解释了,为什么一些时候 【在合成事件and生命周期函数中】,感觉 setState() 的执行是异步的,即 setState() 之后,打印 state 却拿不到它的最新值,还是旧值 【原生事件and异步函数中】,又觉得 setState() 的执行是同步的。

  • 生命周期函数and合成事件中:

因为会被包裹成一个事务执行,而在事务的开始,就会设置 isBatchingUpdates 的值为 true ,就会进入批量更新!!!

  • 原生事件and异步函数:

此时isBatchingUpdates 的值为 false ,不会进入批量更新!!!

<4> partialState合并机制

来浅看一下,合并机制的源码:

  _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];
    }
// 重点:实现了如果setState()的参数是对象的形式,就会进行合并;如果是函数的形式,就不会!!!
    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;
  },

参考1

参考2