Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react的BatchUpdate #27

Open
Rashomon511 opened this issue Jul 26, 2019 · 1 comment
Open

react的BatchUpdate #27

Rashomon511 opened this issue Jul 26, 2019 · 1 comment

Comments

@Rashomon511
Copy link
Owner

前言

我在业务中经常用到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

搬运自:

@lulupy
Copy link

lulupy commented May 7, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants