对于大多数的React开发者,setState多是最经常使用的API之一。React做为View层,经过改变data从而引起UI的更新。React不像Vue这种MVVM库,直接修改data并不能视图的改变,更新状态(state)的过程必须使用setState。 javascript
setState的函数签名以下:java
setState(partialState,callback)
咱们看到setState接受两个参数,一个是partialState
,它是新的state用来更新以前的state。callback
做为回调函数,会在更新结束以后执行。举个常见的例子数组
this.setState({
value: this.state.value + 1
})
上面这个例子执行的结果是将state中value的值增长1。但事实真的如此简单吗?咱们看下面的代码: bash
class Example extends React.Component {
constructor(props) {
super(props);
}
state = {
value: 0
}
render() {
return (
<div> <div>The Value: {this.state.value}</div> <button onClick={::this._addValue}>add Value</button> </div>
);
}
_addValue() {
this.setState({
value: this.state.value + 1
})
this.setState({
value: this.state.value + 1
})
}
}
若是你认为点击"addValue"按妞时每次会增长2的话,说明你可能对setState不是很了解。事实上若是你真的须要每次增长2的话,你的_addValue
函数应该这么写: app
_addValue() {
this.setState((preState,props)=>({
value: preState.value + 1
}))
this.setState((preState,props)=>({
value: preState.value + 1
}))
}
咱们能够看到其实参数partialState
不只能够是一个对象,也能够是一个函数。该函数接受两个参数: 更新前的state(preState
)与当前的属性(props),函数返回一个对象用于更新state。为何会产生这个问题,答案会在后序解答。
其实上面的例子中,若是你真的须要每次增长2的话,你也能够这么写,虽然下面的写法不是很优美:异步
_addValue() {
setTimeout(()=>{
this.setState({
value: this.state.value + 1
});
this.setState({
value: this.state.value + 1
});
},0)
}
你如今是否眉头一皱,发现setState并无这么简单。
ide
关于setState的介绍,官方文档是这么介绍的:函数
Sets a subset of the state. Always use this to mutate
state. You should treat this.state as immutable.工具There is no guarantee that this.state will be immediately updated, so
accessing this.state after calling this method may return the old value.oopThere is no guarantee that calls to setState will run synchronously,
as they may eventually be batched together. You can provide an optional
callback that will be executed when the call to setState is actually
completed.
翻译过来(意译)至关于:
setState用来设置
state
的子集,永远都只使用setState更改state
。你应该将this.state
视为不可变数据。并不能保证this.state会被当即更新,所以在调用这个方法以后访问
this.state
可能会获得的是以前的值。不能保证调用setState以后会同步运行,由于它们可能被批量更新,你能够提供可选的回调函数,在setState真正地完成了以后,回调函数将会被执行。
通篇几个字眼让咱们很难办,不保证、可能,到底何时才会同步更新,何时才会异步更新?可能真的须要咱们研究一下。
React组件继承自React.Component
,而setState是React.Component
的方法,所以对于组件来说setState
属于其原型方法,首先看setState的定义:
function ReactComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback);
}
};
咱们首先看setState,首先调用的是this.updater.enqueueSetState
,先明确this.updater
是什么,在React中每一个组件有拥有一个this.updater
,是用来驱动state
更新的工具对象。当咱们在组件中的构造函数中调用super
时实质调用的就是函数ReactComponent
。其中有:
this.updater = updater || ReactNoopUpdateQueue;
没有传入参数updater
参数时,this.updater
的值就是ReactNoopUpdateQueue
。 而ReactNoopUpdateQueue
实际是没有什么意义的,只至关于一个初始化的过程。而ReactNoopUpdateQueue.enqueueSetState
主要起到一个在非生产版本中警告(warning)的做用。真正的updater
是在renderer
中注入(inject)的。所以若是你在constructor
中尝试调用this.helper.isMounted
会返回false
,代表组件并无安装(mount
),若是你调用setState,也会给出相应的警告。
constructor(props) {
super(props);
//这是指个演示,this.isMounted函数已经被废弃
console.log(this.updater.isMounted())
this.setState({
value: 1
})
}
上面的警告就是ReactNoopUpdateQueue
中负责打印的。告诉咱们在非安装或已卸载的组件上是不能使用setState函数的。
在ReactCompositeComponentMixin
中的函数mountComponent
中有下面的语句:
inst.updater = ReactUpdateQueue;
那咱们来看看ReactUpdateQueue
中的enqueueSetState
:
var ReactUpdatedQueue = {
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue
|| (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
}
咱们经过this.updater.enqueueSetState(this, partialState);
这里的this
是组件的实例,例如在最开始的例子中,this
指的就是函数Example
的实例(class
实质就是函数function
的语法糖)。以下图:
经过执行函数
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
咱们获得的internalInstance
实质就是组件实例的React内部表达,包含了组件实例的内部的一些属性,例如:
internalInstance
的属性不少,但咱们须要关注的只有两个:
_pendingStateQueue
(待更新队列)与
_pendingCallbacks
(更新回调队列)。根据代码
var queue = internalInstance._pendingStateQueue
|| (internalInstance._pendingStateQueue = []);
queue.push(partialState);
若是_pendingStateQueue
的值为null
,将其赋值为空数组[]
,并将partialState
放入待更新state队列_pendingStateQueue
。最后执行enqueueUpdate(internalInstance);
。所以下一步咱们须要研究一下enqueueUpdate
。
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
var ReactUpdates = {
enqueueUpdate: function enqueueUpdate(component) {
ensureInjected();
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
}
首先执行的ensureInjected()
其实也是一个保证ReactUpdates.ReactReconcileTransaction
与batchingStrategy
是否存在,不然给出相应的警告,固然上面两个的做用以后会给出。接下来会根据batchingStrategy.isBatchingUpdates的值作出不一样的行为,若是是true
的话,直接将internalInstance
放入dirtyComponents
,不然将执行batchingStrategy.batchedUpdates(enqueueUpdate, component)
。那么咱们要了解一下batchingStrategy
是干什么的。首先看batchingStrategy
的定义:
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
transaction.perform(callback, null, a, b, c, d, e);
}
},
};
batchingStrategy
实质上就是一种批量更新策略,其属性isBatchingUpdates
表示的是否处于批量更新的过程当中,开始默认值为false。batchedUpdates
就是执行批量更新的方法。当isBatchingUpdates
为false
时,执行transaction.perform(callback, null, a, b, c, d, e)
。不然当isBatchingUpdates
为true
时,直接执行callback
。但在咱们这里,其实不会执行到这儿,由于当isBatchingUpdates
为true
时,直接就将component
中放入dirtyComponents
中。关于代码中的transaction
咱们须要了解下React中的事务Transaction。
关于React中的事务Transaction,源码中给出了下面的ASCII图:
/** * <pre> * wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+ * </pre> */
其实上面的形象的解释了React中的事务Transaction,React Transaction会给方法包装一个个wrapper,其中每一个wrapper
都有两个方法:initialize
与close
。当执行方法时,须要执行事务的perform
方法。perform
方法会首先一次执行wrapper
的initialize
,而后执行函数自己,最后执行wrapper
的close
方法。
定义Transaction须要给构造函数混入Transaction.Mixin,并须要提供一个原型方法getTransactionWrappers
用于返回wrapper数组。下面咱们看下ReactDefaultBatchingStrategy
中的transaction
是如何定义的:
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function() {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
},
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
Object.assign(
ReactDefaultBatchingStrategyTransaction.prototype,
Transaction.Mixin,
{
getTransactionWrappers: function() {
return TRANSACTION_WRAPPERS;
},
}
);
var transaction = new ReactDefaultBatchingStrategyTransaction();
其中wrapperRESET_BATCHED_UPDATES
负责在close
阶段重置ReactDefaultBatchingStrategy
的isBatchingUpdates
为false
。而wrapperFLUSH_BATCHED_UPDATES
负责在close
执行flushBatchedUpdates
。
咱们再次回顾一下更新的过程,若是处于批量更新的过程当中(即isBatchingUpdates为true
),则直接将组件传入dirtyComponents
。若是不是的话,开启批量更新,用事务transaction.perform
执行enqueueUpdate
,这时候isBatchingUpdates
通过上次执行,已是true
,将被直接传入dirtyComponents
。那么传入更新的组件传入dirtyComponents
以后会发生什么?
咱们知道,batchedUpdates
是处于一个事务中的,该事务在close
阶段作了两件事,首先是将ReactDefaultBatchingStrategy.isBatchingUpdates
置为false
,即关闭批量更新的标志位,第二个就是调用了方法ReactUpdates.flushBatchedUpdates
。flushBatchedUpdates
中会涉及到Virtual DOM到真实DOM的映射,这不是咱们这篇文章的重点(最重要的是我本身也没有参透这边的逻辑),这部分咱们只会简要的介绍流程。
//代码有省略
var flushBatchedUpdates = function() {
while (dirtyComponents.length) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
//......
}
};
咱们发如今函数flushBatchedUpdates
中是以事务ReactUpdatesFlushTransaction
的方式执行了函数runBatchedUpdates
,追根溯源咱们来看看runBatchedUpdates
干了什么。
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
//.....
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
//.......
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(
callbacks[j],
component.getPublicInstance()
);
}
}
}
}
首先函数将dirtyComponents
以组件中的_mountOrder
进行了递增排序,其目的就是保证更新顺序,即父组件保证其子组件以前更新。而后在组件中得到setState
完成以后的回调函数,开始执行ReactReconciler.performUpdateIfNecessary
。又得看看这个函数:
performUpdateIfNecessary: function (internalInstance, transaction) {
internalInstance.performUpdateIfNecessary(transaction);
}
performUpdateIfNecessary
执行组件实例的原型方法performUpdateIfNecessary
,咱们再去看看组件实例是如何定义的这个方法:
var ReactCompositeComponentMixin = {
performUpdateIfNecessary: function(transaction) {
//......
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(
transaction,
this._currentElement,
this._currentElement,
this._context,
this._context
);
}
}
}
上面代码是perfromUpdateIfNecessary
的省略版本,主要调用的其中的this.updateComponent
方法:
updateComponent: function( transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext ) {
var inst = this._instance;
var willReceive = false;
var nextContext;
var nextProps;
// 验证组件context是否改变
// ......
// 验证是不是props更新仍是组件state更新
if (prevParentElement === nextParentElement) {
nextProps = nextParentElement.props;
} else {
//存在props的更新
nextProps = this._processProps(nextParentElement.props);
willReceive = true;
}
//根据条件判断是否调用钩子函数componentWillReceiveProps
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
//计算新的state
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate =
this._pendingForceUpdate ||
!inst.shouldComponentUpdate ||
inst.shouldComponentUpdate(nextProps, nextState, nextContext);
if (shouldUpdate) {
this._pendingForceUpdate = false;
this._performComponentUpdate(
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext
);
} else {
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
}
updateComponent
方法已经作了相关的注释,其实里面不只涉及到state的改变致使的从新渲染,还有props的更新致使的从新渲染。在计算新的state
时调用了_processPendingState
:
{
_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 = Object.assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
Object.assign(
nextState,
typeof partial === 'function' ?
partial.call(inst, nextState, props, context) :
partial
);
}
return nextState;
}
}
这一部分代码相对来讲不算是很难,replace
是存在是因为以前被废弃的APIthis.replaceState
,咱们如今不须要关心这一部分,如今咱们能够回答刚开始的问题,为何给setState传入的参数是函数时,就能够解决刚开始的例子。
Object.assign( nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial );
若是咱们传入的是对象
this.setState({value: this.state.value + 1 }); this.setState({value: this.state.value + 1})
咱们如今已经知道,调用setState
是批量更新,那么第一次调用以后,this.state.value
的值并无改变。两次更新的value
值实际上是同样的,因此达不到咱们的目的。可是若是咱们传递的是回调函数的形式,那么状况就不同了,partial.call(inst, nextState, props, context)
接受的state都是上一轮更新以后的新值,所以能够达到咱们预期的目的。
_processPendingState
在计算完新的state以后,会执行_performComponentUpdate
:
function _performComponentUpdate( nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext ) {
var inst = this._instance;
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
var prevProps;
var prevState;
var prevContext;
if (hasComponentDidUpdate) {
prevProps = inst.props;
prevState = inst.state;
prevContext = inst.context;
}
if (inst.componentWillUpdate) {
inst.componentWillUpdate(nextProps, nextState, nextContext);
}
this._currentElement = nextElement;
this._context = unmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
this._updateRenderedComponent(transaction, unmaskedContext);
if (hasComponentDidUpdate) {
transaction.getReactMountReady().enqueue(
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
inst
);
}
}
咱们能够看到,这部份内容涉及到了几方面内容,首先在更新前调用了钩子函数componentWillUpdate
,而后更新了组件的属性(props、state、context),执行函数_updateRenderedComponent
(这部分涉及到render
函数的调用和相应的DOM更新,咱们不作分析),最后再次执行钩子函数componentDidUpdate
。
到目前为止,咱们已经基本介绍完了setState的更新过程,只剩一个部分没有介绍,那就是setState执行结束以后的回调函数。咱们知道,setState函数中若是存在callback,则会有:
if (callback) {
this.updater.enqueueCallback(this, callback);
}
call函数会被传递给this.updater
的函数enqueueCallback
,而后很是相似于setState,callback
会存储在组件内部实例中的_pendingCallbacks
属性之中。咱们知道,回调函数必需要setState真正完成以后才会调用,那么在代码中是怎么实现的。你们还记得在函数flushBatchedUpdates
中有一个事务ReactUpdatesFlushTransaction
:
//代码有省略
var flushBatchedUpdates = function() {
while (dirtyComponents.length) {
if (dirtyComponents.length) {
//从事务pool中得到事务实例
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
//释放实例
ReactUpdatesFlushTransaction.release(transaction);
}
//......
}
};
咱们如今看看ReactUpdatesFlushTransaction
的wrapper是怎么定义的:
var UPDATE_QUEUEING = {
initialize: function() {
this.callbackQueue.reset();
},
close: function() {
this.callbackQueue.notifyAll();
},
};
咱们看到在事务的close
阶段定义了this.callbackQueue.notifyAll()
,即执行了回调函数,经过这种方法就能保证回调函数必定是在setState真正完成以后才执行的。到此为止咱们基本已经解释了setState大体的流程是怎样的,可是咱们仍是没有回答以前的一个问题,为何下面的两种代码会产生不一样的状况:
//未按预期执行
_addValue() {
this.setState({
value: this.state.value + 1
})
this.setState({
value: this.state.value + 1
})
}
//按预期执行
_addValue() {
setTimeout(()=>{
this.setState({
value: this.state.value + 1
});
this.setState({
value: this.state.value + 1
});
},0)
}
这个问题,其实真的要追本溯源地去讲,是比较复杂的,咱们简要介绍一下。在第一种状况下,若是打断点追踪你会发现,在第一次执行setState前,已经触发了一个 batchedUpdates,等到执行setState时已经处于一个较大的事务,所以两个setState都是会被批量更新的(至关于异步更新的过程,thi.state.value值并无当即改变),执行setState只不过是将二者的partialState
传入dirtyComponents
,最后再经过事务的close
阶段的flushBatchedUpdates
方法去执行从新渲染。可是经过setTimeout
函数的包装,两次setState都会在click触发的批量更新batchedUpdates
结束以后执行,这两次setState会触发两次批量更新batchedUpdates,固然也会执行两个事务以及函数flushBatchedUpdates
,这就至关于一个同步更新的过程,天然能够达到咱们的目的,这也就解释了为何React文档中既没有说setState是同步更新或者是异步更新,只是模糊地说到,setState并不保证同步更新。 这篇文章对setState的介绍也是比较浅显的,可是但愿能起到一个抛砖迎玉的做用。setState之因此须要会采用一个批量更新的策略,其目的也是为了优化更新性能。但对于日常的使用中,虽然咱们不会关心或者涉及到这个问题,可是咱们仍然可使用React开发出高性能的应用,我想这也就是咱们喜欢React的缘由:简单、高效!