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
Hello {this.props.name}! {this.state.now}
; } 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
吗?其中一个就是FLUSH_BATCHED_UPDATES
FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) // 对于dirtyComponents的操作,就在这里进行 };
flushBatchedUpdates
使用了另外一个Transaction:ReactUpdatesFlushTransaction
。
ReactUpdatesFlushTransaction结构
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方法。