一个关于React.Component.setState的问题

React组件从新渲染的条件是:
B.只要调用this.setState()就会发生从新渲染。
C.必须调用this.setState()且传递不一样于当前this.setState()的参数,才会引起从新渲染。

本文将从三方面说明这个问题为何选择C。或者说为何 setState 在传递不一样当前 this.State 的参数,才会引起组件从新渲染。javascript

结论

我仍是想选择Bhtml

引用规范

TL;DRjava

下面是 React 官方对于 setState 的说明,翻译的做者是我。在这段文章中,对setState说明了两点。react

  1. setState是异步的。
  2. setState会(always)致使从新渲染,当且仅当shouldComponentUpdate()返回了false的时候不会。

读者能够直接进入实验部分。git

React原文中关于setState的说明:github

setState(updater[, callback])

setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.架构

setState() 会将当前组件的 state 的更改所有推入队列,而且通知 React 这个组件和他的孩子们须要更新这些状态并从新渲染。这是开发者常用的用来更新 UI 的方法(不论是在事件响应中仍是处理服务端的返回)。app

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.less

setState()看成一个更新的_请求_而不是一个更新的函数。为了更好的性能,React 可能会延迟这些更新,将几个组件的更新合并在一块儿执行。React不保证这个状态的更新是当即执行的。dom

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

setState()并非当即更新这些组件,而是可能延后批量更新。这个问题致使若是开发者在setState()以后当即去访问this.state可能访问的不是最新的状态。然而,开发者仍是可使用一些方法来访问到最新的state的。好比在组件生命周期的componentDidUpdate,或者在setState的回调函数中。固然了若是你须要依赖以前的状态来更新当前的状态,看一看这个updater

setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.

setState() 确定老是一直毫无疑问的会致使render函数被从新调用[1],除非shouldComponentUpdate()返回了false。若是开发者使用了可变的变量或者更新的逻辑没法在shouldComponentUpdate()中编写,那为了减小无心义的从新渲染,应该仅仅在肯定当前的新状态和旧状态不同的时候调用setState()。【但愿读者不要误会,React是让开发者本身作这个比较。不是React替你作好了的。】

[1].(咱们把这种行为叫作从新渲染)

The first argument is an updater function with the signature:

setState()能够接受两个参数,第一个参数叫作updater的函数,函数的签名以下:

(prevState, props) => stateChange

prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from prevState and props. For instance, suppose we wanted to increment a value in state by props.step:

prevState是组件以前的状态(引用关系)。prevState不该该被直接更改,而是应该新建一个Object来表示更新后的状态。举个例子:若是开发者想要更新state中的counter给它加一。应该按照下面的作法。

this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});

Both prevState and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with prevState.

React 保证 updater 接受的 prevStateprops 都是最新的。而且updater 的返回是被浅拷贝merge进入老状态的。

The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.

setState()的第二个参数是可选的回调函数。在state更新完成后他会被执行一次。整体上来讲,React官方更推荐在componentDidUpdate()中来实现这个逻辑。

You may optionally pass an object as the first argument to setState() instead of a function:

开发者还能够在第一次参数的位置不传入函数,而是传入一个对象。

setState(stateChange[, callback])

This performs a shallow merge of stateChange into the new state, e.g., to adjust a shopping cart item quantity:

像上面这种调用方式中,stateChange会被浅拷贝进入老状态。例如开发者更新购物车中的商品数量的代码应该以下所示:

this.setState({quantity: 2})

This form of setState() is also asynchronous, and multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of:

这种形式的setState()也是异步的,并且在一个周期内的屡次更新会被批量一块儿更新。若是你想更新状态里面的数量,让他一直加一加一。代码就会以下所示

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the previous state, we recommend using the updater function form, instead:

这样队列的调用会重写以前的更新,因此最后数量仅仅会被更新一次。在这种新状态依赖老状态数据的状况下,React官方推荐你们使用函数。以下所示:

this.setState((prevState) => {
  return {quantity: prevState.quantity + 1};
});

实验验证

设计实验来验证React官方说法的正确性。实验采用基于控制变量法的对照试验。

基于 React16( 引入了 Fiber 架构)和 React 0.14 分别进行实验。至于React 15的问题,留给读者本身吧。

编写以下组件代码:

class A extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            a:1
        }
        this._onClick = this.onClick.bind(this);
    }
    onClick(){
        this.setState({a:2}) // 替换点
    }
    render(){
        console.log('rerender');
        return(
            <div onClick={this._onClick}>
                <p>a: {this.state.a}</p>
                <p>{Math.random()}</p>
            </div>
        );
    }
}

若是须要能够读者自行粘贴从新复现实验。

更新的标准:界面中显示的随机数是否发生了变化。固然也能够观察 Console中是否出现了 rerender

React 0.14.5 实验结果以下所示:

条件 不编写shouldComponentUpdate()方法 return false; return true;
setState({}) 更新 不更新 更新
setState(null) 更新 不更新 更新
setState(undefined) 更新 不更新 更新
setState(this.state) 更新 不更新 更新
setState(s=>s) 更新 不更新 更新
setState({a:2}) 更新 不更新 更新

React 16 实验结果以下所示:

条件 不编写shouldComponentUpdate()方法 return false; return true;
setState({}) 更新 不更新 更新
setState(null) 不更新 不更新 不更新
setState(undefined) 不更新 不更新 不更新
setState(this.state) 更新 不更新 更新
setState(s=>s) 更新 不更新 更新
setState({a:2}) 更新 不更新 更新

可见对于setState()来讲,React 在不一样版本的表现不尽相同。

React 0.14中可能更符合只要调用setState()就会进行更新。

React 16.3.2中只有在传递null和undefined的时候才不会更新,别的时候都更新。

源码说明

React 16中是这样的:

https://github.com/facebook/r...

1. const payload = update.payload;
2. let partialState;
3. if (typeof payload === 'function') {
4.       partialState = payload.call(instance, prevState, nextProps);
5. } else {
6.       // Partial state object
7.       partialState = payload;
8. }
9. if (partialState === null || partialState === undefined) {
10.   // Null and undefined are treated as no-ops.
11.   return prevState;
12.}
13.// Merge the partial state and the previous state.
14.return Object.assign({}, prevState, partialState);

React 14中是这样的:

证有容易,证无难,因此我要顺着整条链路的源码的展现一遍。

TL;DR

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.调用

setState({})

2.原型方法

ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback);
  }
};

3.入队方法

enqueueSetState: function (publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
    if (!internalInstance) {
      return;
    }
    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);
    enqueueUpdate(internalInstance);
  },

internalInstance 是一个 ReactCompositeComponentWrapper,大概就是包装着ReactComponent实例的一个对象。

4.入队

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

5.更具当前的批量策略来决定更新方法

function enqueueUpdate(component) {
  ensureInjected();

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

  dirtyComponents.push(component);
}

6.能够看到直到这里都没人管这个东西到底更新的是什么。
7.剩下的事情基本就是垃圾回收处理现场的事情了。
8.处理完以后会

ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);

9.请求更新队列,进行更新

var flushBatchedUpdates = function () {
  // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
  // array and perform any updates enqueued by mount-ready handlers (i.e.,
  // componentDidUpdate) but we need to check here too in order to catch
  // updates enqueued by setState callbacks and asap calls.
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      transaction.perform(runBatchedUpdates, null, transaction);<!--here-->
      ReactUpdatesFlushTransaction.release(transaction);
    }

    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
  }
};

10.更新

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;

  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.
  dirtyComponents.sort(mountOrderComparator);

  for (var i = 0; i < len; i++) {
    // If a component is unmounted before pending changes apply, it will still
    // be here, but we assume that it has cleared its _pendingCallbacks and
    // that performUpdateIfNecessary is a noop.
    var component = dirtyComponents[i];

    // If performUpdateIfNecessary happens to enqueue any new updates, we
    // shouldn't execute the callbacks until the next render happens, so
    // stash the callbacks first
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;
<!--here-->
    ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);

    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
      }
    }
  }
}

11.最重要的来了

performUpdateIfNecessary: function (transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(this, this._pendingElement || this._currentElement, transaction, this._context);
    }

    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
    }
  },

12.更新

updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
    //... props context 更新
    var nextState = this._processPendingState(nextProps, nextContext);

    var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext);

    if (shouldUpdate) {
      this._pendingForceUpdate = false;
      // Will set `this.props`, `this.state` and `this.context`.
      this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
    } else {
      // If it's determined that a component should not update, we still want
      // to set props and state but we shortcut the rest of the update.
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
  },

13.计算state

_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];
    }

    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;
  },

14.就这样了。

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;

15.流程中没有任何比较操做。