Rashomon511/MyBlog

react的BatchUpdate

Rashomon511 opened this issue · 1 comments

前言

我在业务中经常用到setState,自以为对setState已经非常对了解,直到某一天遇到一个关于state批量更新的面试题,发现自己好像对setState的认识也没有那么全面,所以写这篇文章主要是希望彻底搞清楚setState的更新机制。

setState的更新策略

React中的setState有Batch模式(批量更新模式)和普通模式。
普通模式下,setState能够即时更新state,重新调用 render 方法,然后把render方法所渲染的最新的内容显示到页面上。
Batch模式下,React不会立刻修改state。而是把这个对象放到一个更新队列中,稍后才会从队列中把新的状态提取出来合并到 state中,然后再触发组件更新。

批量更新

react的setState是一个异步方法,React 会将所有的 setState 方法打包成一次进行更新,每次修改 state 都会进行更新。这样的设计主要是为了提高 UI 更新的性能,

我们知道 React 中 state 的改变会导致 UI 的更新。如果想要进行同步操作逻辑有两种方法。
1.通过异步实现

 componentDidMount(){
    setTimeout(() => {
      this.setState({
        value: this.state.value + 1
      })
      console.log('value', this.state.value)   
    }, 0)
}

2.通过回调函数实现

this.setState({
      value: this.state.value + 1
    }, () => {
      console.log('value', this.state.value) 
})

判断批量更新

在更新逻辑的部分,可以看到 React 会通过 batchingStrategy.isBatchingUpdates 判断当前的逻辑状态下是否需要进行批量更新

  • 如果不是,那么就直接进行页面的批量更新,将之前累积的所有状态一次更新到组件上。
  • 如果是,那么所有的组件状态不进行立即更新,而是将组件状态存放在一个叫 dirtyComponents 的数组中去,等待下次更新时机的到来再进行更新
function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent's
  // _renderValidatedComponent) assume that calls to render aren't nested;
  // verify that that's the case. (This is called by each top-level update
  // function, like setProps, setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
}

为了实现上面的逻辑判断,React 将整个的函数执行过程包裹上了 Transaction,在函数执行前与执行后分别有 initialize 和 close 两个方法。这样的话 React 就有时机在函数执行过程中,涉及到 setState 的执行,都将缓存下来,在 close 的时候进入到 React 的 state 更新逻辑进行更新判断操作,并最终更新到前台的 DOM 上。
initialize、close源码

setState 的源码实现

在 setState 的源码实现中,传递过来的参数就被定义成了 partialState,从参数名以及参数的说明中就可以看到,这只是 state 的一部分。
默认都会调用 this.updater.enqueueSetState(this, partialState) 将 state 放进更新队列中去。
而如果有传递回调函数过来的话,会执行 this.updater.enqueueCallback(this, callback).

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
    typeof partialState === 'function' ||
    partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
    'function which returns an object of state variables.'
  );
  if (__DEV__) {
    warning(
      partialState != null,
      'setState(...): You passed an undefined or null state object; ' +
      'instead, use forceUpdate().'
    );
  }
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback);
  }
};

更新队列 ReactUpdateQueue 的定义

在上面的 setState 定义中,我们可以看到有一个 updater 的调用

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.isReactComponent = {};

enqueueSetState 的定义

  enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState'
    );

    if (!internalInstance) {
      return;
    }

    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

enqueueUpdate的实现

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

ReactUpdates 中的 enqueueUpdate

function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent's
  // _renderValidatedComponent) assume that calls to render aren't nested;
  // verify that that's the case. (This is called by each top-level update
  // function, like setProps, setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
}

ensureInjected实现:

function ensureInjected() {
  invariant(
    ReactUpdates.ReactReconcileTransaction && batchingStrategy,
    'ReactUpdates: must inject a reconcile transaction class and batching ' +
    'strategy'
  );
}

注入了两个部分,ReactReconcileTransaction 以及 batchingStrategy。
ReactReconcileTransaction 主要用于在更新 state 时,页面 UI 元素的修正以及在执行生命周期函数时,处理好生命周期函数与其他用户自定义函数之间的执行顺序与逻辑

现在我们可以看一下面试题了

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;
  }
};
// 0 0 2, 3

搬运自:

请问一下文中react的版本是多少啊?