连续状态更新
如果连续的执行setState()时,React并不会进行多次重新渲染,而是只会渲染一次,避免了多余的DOM操作,这样的高效连续更新是怎么做到的呢?这就要从setState的实现说起。
pendingState
React复合组件除了state,还包含一个内部属性,叫做_pendingState。当复合组件处在MOUNTING状态或者RECEIVING_PROPS状态时,setState的结果就会被写入到_pendingState中;而当复合组件既不处在MOUNTING中,也不处在RECEIVING_PROPS中,就会发起一个新事务,在事务中完成组件的状态更新。这样一来,无论调用多少次setState,只要当前尚处在MOUNTING或者RECEIVING_PROPS状态时,setState就会将结果赋值给_pendingState,而不会额外发起新的事务。
pendingState会优先于state作为nextState传递给_receiveProsAndState,完成状态更新。
连续setState
当首次调用setState后,React会启用一个事务,在事务中进行状态更新。然而,setState有可能会被连续调用,当react已经处在事务中时,第二次调用setState就不会再发起新的事务,而是直接将状态更新写入到pendingState中。等到调用receiveProps时,_pendingState被赋值给nextState,并将状态更新为RECEIVING_STATE,进一步完成渲染。换言之,如果组件的状态已经变更成为RECEIVING_STATE,再调用setState,此时就会产生一个新的事务。
replaceState: function(completeState) {
var compositeLifeCycleState = this._compositeLifeCycleState;
this._pendingState = completeState;
// 只有既不在挂载中、也不在接受属性中,才发起新事务,否则都直接更新pendingState
if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState;
this._pendingState = null;
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
// _receivePropsAndState会使用pendingState作为待渲染数据,
// 同时将复合组件状态更新为RECEIVING_STATE,此时若调用setState,就会发起一个新事务
transaction.perform(
this._receivePropsAndState,
this,
this.props,
nextState,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._compositeLifeCycleState = null;
}
},
多数情况下,连续的setState只会产生一个事务。当一个事务已经启用后,调用setState时,结果会被缓存在pendingState中,由于setState会触发调用receiveProps,复合组件状态会被设置为RECEIVING_PROPS,直至_receivePropsAndState执行完毕,在执行_receivePropsAndState时,会优先取pendingState作为nextState,同时将复合组件状态更新为RECEIVING_STATE。因此,即便事务开启后,没有新的事务诞生,但由于setState的结果都写入到了pendingState中,在实际的渲染开始前,仍然会将最新改变的状态作为待渲染的数据。
当然,React只是尽可能的减少发起新事务,但不能彻底避免。如果在_receivePropsAndState执行中,当前复合组件状态已经更新为RECEIVING_STATE,而此刻又调用了setState,就会导致一个新事务被创建。