React-函数setState的执行

December 23, 2018 ... ☕️ 5 min read

setState是React里使用频率最高的的一个操作,React的状态更新,不同于vue的直接this.data设置,需要都要通过这个函数进行。

在使用中,总结setState的3个特性:

1、异步更新,即调用setState之后立刻获取更新值,通常不会取到最新的值;
2、合并更新,例如在不同的生命周期(componentWillMount、omponentDidMount等)进行的状态更新,最终只会触发一次;
3、可以传入对象或者函数。

本篇依然使用单步调试的方法,首先在之前的例子里添加state和相关的setState操作:

class Hello extends React.Component {
       constructor(props) {
          super(props);
          this.state = {now: '2018'};
        }
        render() {
          return <div>Hello {this.props.name}! {this.state.now}</div>;
        }
        componentDidMount() {
          this.setState({
            now: '123456'
          });
        }
}

调用栈

1、setState函数的处理

this.setState是定义在ReactComponent原型上的方法,定义在ReactBaseClasses.js。

-this.updater.enqueueSetState // 这里updater是ReactNoopUpdateQueue,执行Component构建函数时添加

如果定义了回调函数,那么会把回调函数也放进待处理队列enqueueCallback。

enqueueSetState(
publicInstance,
partialState // {now: '123456'}
)
 
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

internalInstance是从实例的属性_reactInternalInstance查找到ReactCompositeComponentWrapper对象,这个对象是之前渲染的时候‘脱壳’TopLevelWrapper之后重新包装的,并通过属性建立了反向链接,位置在上一篇步骤2.3)。

-internalInstance._pendingStateQueue.push(partialState) // 将待处理的state变化放入实例的待处理队列
 
-enqueueUpdate(internalInstance)
=ReactUpdates.enqueueUpdate
 
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
 
  dirtyComponents.push(component);
 
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
ReactUpdates.enqueueUpdate函数的处理,决定了state是同步或是异步更新。

2、更新队列的处理

那么state具体是什么时候更新呢?跳回去想想,enqueueUpdate,不管是否初次渲染,最后都是放在transaction(ReactDefaultBatchingStrategyTransaction)里执行的,那么肯定会在最后执行releaseAll,就需要把更新放在这里。

记得ReactDefaultBatchingStrategyTransaction里定义了两个transactionWrapper吗?其中一个就是FLUSHBATCHEDUPDATES

FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) // 对于dirtyComponents的操作,就在这里进行
};

flushBatchedUpdates使用了另外一个Transaction:ReactUpdatesFlushTransaction。

ReactUpdatesFlushTransaction结构

react-transaction-flush-transaction

ReactUpdatesFlushTransaction重写了peform,函数内部还包装了一个transaction的perform:ReactReconcileTransaction.perform,所以会有两层Transaction执行过程。

调用过程

-ReactUpdatesFlushTransaction.perform(runBatchedUpdates, null, transaction)
=Transaction.perform.call(this, this.reconcileTransaction.perform, this.reconcileTransaction, runBatchedUpdate, null, transaction)
  -ReactUpdatesFlushTransaction.initializeAll
  -this.reconcileTransaction.perform.call(this.reconcileTransaction, runBatchedUpdates, null, transaction)
    -this.reconcileTransaction.initializeAll
    -runBatchedUpdates.call(null, transaction)
    -this.reconcileTransaction.closeAll
  -ReactUpdatesFlushTransaction.closeAll

可以看到,剥开了前面包上去的wrapper,最终执行的函数就是runBatchedUpdates

runBatchedUpdates
-dirtyComponents.sort(mountOrderComparator) // 按_mountOrder从小到大排序
-updateBatchNumber++ // 在递归子组件的时候防止重复更新
for (var i = 0; i < len; i++) {
	...
	-ReactReconciler.performUpdateIfNecessary(
		component,
		transaction.reconcileTransaction,
		updateBatchNumber
	)
	...
}
 
-this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context)
// this == 修改前的ReactCompositeComponentWrapper,包含_pendingStateQueue

updateComponent包含各种状态和属性判断,还有生命周期函数调用:

1)判断context是否更改; 2)判断props是否更改(如更改,调用生命周期方法componentWillReceiveProps); 3)判断state更改,_processPendingState合并处理状态更改:

this._performComponentUpdate

调用生命周期方法componentWillUpdate 更新currentElement指向的对象,更改instance指向的实例props、state、context。

4)更新属性

_renderedComponent
-this._updateRenderedComponent //调用render方法
-ReactReconciler.receiveComponent
=internalInstance.receiveComponent // 接收新的element,更新组件
=this.updateComponent // this == ReactDOMComponent

更新props,更新DOM(DOM更新暂时不看)

5)调用生命周期方法componentDidUpdate

小记 state有可能会立即更新吗?

如果是在某次batchedUpdates的处理过程中(比如首次渲染),即batchingStrategy.isBatchingUpdates为true,那么setState只负责把state和callback放进队列里,然后就接着执行下面的函数部分了。此时,state的更新就会不同步。

如果是setState发生的时候,并没有进行中的batchedUpdate,就会主动调用batchingStrategy.batchedUpdates方法,开始一轮新的batchedUpdates,之后的处理和上面一样,state和callBack会被放入队列,继续执行其他函数。

特殊情况,比如给setState包了timeout、interVal、写进了异步请求处理函数中,都会使setState所在的函数,脱离了原本的React处理流程,就会造成setState每次都同步更新。

→延申阅读Why isn’t this.state updated immediately?

目前依然没涉及到实际的react节点变为HTML节点,即_mountImageIntoNode方法。

#react