Axiu Blog
`setState`之后立刻获取更新值,通常不会取到最新的值; 2、合并更新,例如在不同的生命周期(`componentWillMount`、`omponentDidMount`等)进行的状态更新,最终只会触发一次; 3、可以传入对象或者函数。 本篇依然使用单步调试的方法,首先在之前的例子里添加`state`和相关的`setState`操作: c
`setState`是`React`里使用频率最高的的一个操作,`React`的状态更新,不同于`vue`的直接`this.data`设置,需要都要通过这个函数进行。 在使用中,总结`setState`的3个特性: 1、异步更新,即调用`setState`之后立刻获取更新值,通常不会取到最新的值; 2、合并更新,例如在不同的生命周期(`componentWillMount`、`ompone
`setState`是`React`里使用频率最高的的一个操作,`React`的状态更新,不同于`vue`的直接`this.data`设置,需要都要通过这个函数进行。 在使用中,总结`setState`的3个特性: 1、异步更新,即调用`setState`之后立刻获取更新值,通常不会取到最新的值; 2、合并更新,例如在不同的生命周期(`componentWillMount`、`ompone
React-函数setState的执行
Max

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

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

1、异步更新,即调用setState之后立刻获取更新值,通常不会取到最新的值;
2、合并更新,例如在不同的生命周期(componentWillMountomponentDidMount等)进行的状态更新,最终只会触发一次;
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,不管是否初次渲染,最后都是放在transactionReactDefaultBatchingStrategyTransaction)里执行的,那么肯定会在最后执行releaseAll,就需要把更新放在这里。

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

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指向的实例propsstatecontext

4)更新属性

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

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

5)调用生命周期方法componentDidUpdate

小记

state有可能会立即更新吗?

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

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

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

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

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

Comments