React setState
不知道什么时刻最先,许多人最先以为setState是异步操纵,所谓的异步操纵,就是我们在实行了setState以后,马上经由历程this.state.xxx不能拿到更新以后的值。如许的认知其实有一种先入为主的认识,或许是遭到许多不知名博主的不科学谈吐致使的毛病认知,也有多是一样平常开辟历程当中积聚的履历。毕竟大部份开辟写setState如许的要领,都是在组件的生命周期(如componentDidMount
、componentWillMount
)中,或许react的事宜处置惩罚机制中,这类教科书式的写代码体式格局,基础不会碰到有数据非常。
虽然官方文档对setState这类同步行动语焉不详,然则我们能够发明某些状况下,setState是真的能够同步猎取数据的。经由历程本文我们能够相识react这方面的事情原理,关于我们的思索开辟计划,处理疑难问题,防止不必要的毛病,或许会有不少协助。
我们先来讲结论:
在React中,假如是由React激发的事宜处置惩罚(比方经由历程onClick激发的事宜处置惩罚,componentWillMount等生命周期),挪用setState不会同步更新this.state;除此之外的setState挪用会同步实行this.state。所谓“除此之外”,指的是绕过React经由历程addEventListener直接增加的事宜处置惩罚函数,另有经由历程setTimeout/setInterval发生的异步挪用。
不想看长篇大论的同砚,到这里就能够完毕了。想相识原理的同砚请继承观光。。
用过angular框架的同砚或许记得angular的代码情势中有一个$timeout
如许的挪用要领,和setTimeout
功用基础一致,然则setTimeout
却不能及时触发UI的更新。这是由于$timeout
比setTimeout
增加了对UI更新(脏搜检)的处置惩罚,在延时完毕后马上挪用更新要领更新UI的衬着。一样的原理,我们必需运用react指定的体式格局更新state才同步UI的衬着,由于react掌握下的事宜会同步处置惩罚UI的更新。而直接运用this.state.xxx = xxx
如许的体式格局仅仅转变了数据,没有转变UI,这就不是React提倡的reactive programing了。
实际上,在react的源码中我们会发明,大部份react掌握下的事宜或生命周期,会挪用batchedUpdates
(检察以下代码)。这个要领会触发component衬着的状况isBatchingUpdates
。一样的,react的事宜监听机制会触发batchedUpdates
要领,一样会将isBatchingUpdates
状况置为true。
// 更新状况
batchingStrategy.batchedUpdates(method, component);
在组件衬着状况isBatchingUpdates
中,任何的setState都不会触发更新,而是进入行列。除此之外,经由历程setTimeout/setInterval发生的异步挪用是能够同步更新state的。如许的解说比较笼统,我们能够直接依据以下源码最先明白。
setState
下面我们来看下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.
*
* There 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.
*
* When a function is provided to setState, it will be called at some point in
* the future (not synchronously). It will be called with the up to date
* component arguments (state, props, context). These values can be different
* from this.* because your function may be called after receiveProps but before
* shouldComponentUpdate, and this new state, props, and context will not yet be
* assigned to this.
*
* @param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* @param {?function} callback Called after state is updated.
* @final
* @protected
*/
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
依据源码中的解释,有这么一句话。
There is no guarantee that
this.state
will be immediately updated, so accessing
this.state
after calling this method may return the old value.
也许意义就是setState不能确保及时更新state,官方从来没有说过setState是一种异步操纵,但也没有否定,只是通知我们什么时刻会触发同步操纵,什么时刻是异步操纵。所以我们事情中万万不要被一些民间偏方蒙蔽双眼,多看看源代码,发明原理的同时,还能够发明许多好玩的东西,开源库的优点就是在于我们能在源码中发明真谛。
我们在源码的这段解释里也能看到setState的一些风趣弄法,比方
// 在回调中操纵更新后的state
this.setState({
count: 1
}, function () {
console.log('# next State', this.state);
});
// 以非对象的情势操纵
this.setState((state, props, context) => {
return {
count: state.count + 1
}
});
回到正题,源码中setState实行了this.updater.enqueueSetState
要领和this.updater.enqueueCallback
要领 ,临时不管enqueueCallback
,我们关注下enqueueSetState
的作用。
enqueueSetState
下面是enqueueSetState
的源码:
/**
* Sets a subset of the state. This only exists because _pendingState is
* internal. This provides a merging strategy that is not available to deep
* properties which is confusing. TODO: Expose pendingState or don't use it
* during the merge.
*
* @param {ReactClass} publicInstance The instance that should rerender.
* @param {object} partialState Next partial state to be merged with state.
* @internal
*/
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
enqueueSetState
如其名,是一个行列操纵,将要变动的state一致插进去行列,待逐一处置惩罚。行列数据_pengdingStateQueue
会挂载在一个组件对象上internalInstance
,关于internalInstance
想要相识下的同砚,能够参考下react源码中的ReactInstanceMap
这个观点。
行列操纵完成以后,就最先真正的更新操纵了。
enqueueUpdate
更新要领enqueueUpdate
的源码以下:
/**
* Mark a component as needing a rerender, adding an optional callback to a
* list of functions which will be executed once the rerender occurs.
*/
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setProps, setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
第一次实行setState的时刻,能够进入if语句,碰到内里的return语句,停止实行。假如不是正处于建立或更新组件阶段,则处置惩罚update事件。
第二次实行setState的时刻,进入不了if语句,将组件放入dirtyComponents。假如正在建立或更新组件,则临时先不处置惩罚update,只是将组件放在dirtyComponents数组中。
enqueueUpdate
包含了React防止反复render的逻辑。参考源码中batchedUpdates
的挪用状况,mountComponent
和updateComponent
要领在实行的最最先,会挪用到batchedUpdates
举行批处置惩罚更新,这些是react实例的生命周期,此时会将isBatchingUpdates
设置为true,也就是将状况标记为如今正处于更新阶段了。以后React以事件的体式格局处置惩罚组件update,事件处置惩罚完后会挪用wrapper.close()
, 而TRANSACTION_WRAPPERS
中包含了RESET_BATCHED_UPDATES
这个wrapper,故终究会挪用RESET_BATCHED_UPDATES.close()
, 它终究会将isBatchingUpdates
设置为false。
听不懂?听不懂没紧要。。我们会一句句理会。
enqueueUpdate
和batchingStrategy
的观点我们放一同斟酌。
batchingStrategy
简朴直译叫做批量处置惩罚战略。这个是React处置惩罚批量state操纵时的精华,源码以下:
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
callback(a, b, c, d, e);
} else {
transaction.perform(callback, null, a, b, c, d, e);
}
}
};
如enqueueUpdate
源码中所述,每次实行更新前,会预先推断isBatchingUpdates
是不是处置惩罚批量更新状况,如我们罕见的周期诸如componentWillMount
、componentDidMount
,都是处于isBatchingUpdates
的批量更新状况,此时实行的setState操纵,不会进入if语句实行update,而是进入dirtyComponents
的客栈中。
这就是文章开首所说的栗子,为何setTimeout实行的setState会同步更新state,而react生命周期中实行的setState只能异步更新的缘由。只要react掌握下的事宜周期,会实行batchedUpdates
切换isBatchingUpdates
状况,保证批量操纵能被截获并插进去客栈。其他事宜都和同步实行update要领无异。
实行batchedUpdates
以后,会马上将isBatchingUpdates
赋值为true,表明此时行将进入更新状况,一切以后的setState进入行列守候。
这里我们以一般的setTimeout为例,实行一次更新。营业代以下:
setTimeout(function () {
this.setState({
count: this.state.count + 1
});
}, 0);
实行时isBatchingUpdates
默许是false,所以当我们实行到batchedUpdates
这一步的时刻,源码中alreadyBatchingUpdates
被赋值为false,我们会跳过if进入else前提,实行下一阶段transaction.perform
。
transaction.perform
perform
为我们实行了UI更新的第一步预操纵。这里我们会实行一系列更新初始化操纵和更新状况的封闭。该要领做了try-catch掌握,大批数据操纵有能够激发毛病exception,perform要领在这里对毛病做了截获掌握。
/**
* Executes the function within a safety window. Use this for the top level
* methods that result in large amounts of computation/mutations that would
* need to be safety checked. The optional arguments helps prevent the need
* to bind in many cases.
*
* @param {function} method Member of scope to call.
* @param {Object} scope Scope to invoke from.
* @param {Object?=} a Argument to pass to the method.
* @param {Object?=} b Argument to pass to the method.
* @param {Object?=} c Argument to pass to the method.
* @param {Object?=} d Argument to pass to the method.
* @param {Object?=} e Argument to pass to the method.
* @param {Object?=} f Argument to pass to the method.
*
* @return {*} Return value from `method`.
*/
perform: function (method, scope, a, b, c, d, e, f) {
!!this.isInTransaction() ? "development" !== 'production' ? invariant(false, 'Transaction.perform(...): Cannot initialize a transaction when there ' + 'is already an outstanding transaction.') : invariant(false) : void 0;
var errorThrown;
var ret;
try {
this._isInTransaction = true;
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// one of these calls threw.
errorThrown = true;
this.initializeAll(0);
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
}
源码中实行了一些毛病的预判,终究我们真正实行的是closeAll
要领。关于state的数据更新,从close最先。
close
/**
* Invokes each of `this.transactionWrappers.close[i]` functions, passing into
* them the respective return values of `this.transactionWrappers.init[i]`
* (`close`rs that correspond to initializers that failed will not be
* invoked).
*/
closeAll: function (startIndex) {
!this.isInTransaction() ? "development" !== 'production' ? invariant(false, 'Transaction.closeAll(): Cannot close transaction when none are open.') : invariant(false) : void 0;
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// wrapper.close threw.
errorThrown = true;
if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
// The closer for wrapper i threw an error; close the remaining
// wrappers but silence any exceptions from them to ensure that the
// first error is the one to bubble up.
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}
在引见close之前,我们先相识下两个对象。也就是源码中的this.transactionWrappers
。他在初始被赋值为[FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]
,也就是以下两个对象,在源码被称作为wrapper
。
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
源码中我们看到closeAll
实行了一次for轮回,并实行了每一个wrapper
的close
要领。
RESET_BATCHED_UPDATES
的close要领很简朴,把isBatchingUpdates
更新中这个状况做了一个close的操纵,也就是赋值为false,表明本次批量更新已完毕。
FLUSH_BATCHED_UPDATES
的close要领实行的是flushBatchedUpdates
要领。
flushBatchedUpdates
var flushBatchedUpdates = function () {
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
// array and perform any updates enqueued by mount-ready handlers (i.e.,
// componentDidUpdate) but we need to check here too in order to catch
// updates enqueued by setState callbacks and asap calls.
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
我们临时不管asap是什么,能够看到flushBatchedUpdates
做的是对dirtyComponents
的批量处置惩罚操纵,关于行列中的每一个component实行perform更新。这些更新都邑实行真正的更新要领runBatchedUpdates
。
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
!(len === dirtyComponents.length) ? "development" !== 'production' ? invariant(false, 'Expected flush transaction\'s stored dirty-components length (%s) to ' + 'match dirty-components array length (%s).', len, dirtyComponents.length) : invariant(false) : void 0;
// Since reconciling a component higher in the owner hierarchy usually (not
// always -- see shouldComponentUpdate()) will reconcile children, reconcile
// them before their children by sorting the array.
dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
// If a component is unmounted before pending changes apply, it will still
// be here, but we assume that it has cleared its _pendingCallbacks and
// that performUpdateIfNecessary is a noop.
var component = dirtyComponents[i];
// If performUpdateIfNecessary happens to enqueue any new updates, we
// shouldn't execute the callbacks until the next render happens, so
// stash the callbacks first
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
// Duck type TopLevelWrapper. This is probably always true.
if (component._currentElement.props === component._renderedComponent._currentElement) {
namedComponent = component._renderedComponent;
}
markerName = 'React update: ' + namedComponent.getName();
console.time(markerName);
}
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction);
if (markerName) {
console.timeEnd(markerName);
}
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
runBatchedUpdates
中的中心处置惩罚是ReactReconciler.performUpdateIfNecessary
。
/**
* If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`
* is set, update the component.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
}
}
在这里我们终究又看到了我们熟习的_pendingStateQueue
,还记得这是什么吗?是的,这就是state的更新行列,performUpdateIfNecessary
做了行列的特别推断,防止致使毛病更新。
接下来的这段代码是updateComponent
,源码内容比较长,然则我们能够看到许多熟知的生命周期要领的身影,比方说componentWillReceiveProps
和shouldComponentUpdate
,做了component的更新推断。
这部份要领一致归属于
ReactCompositeComponentMixin
模块,有兴致相识全部生命周期的同砚能够参考下源码中的该模块源码,这里我们不再扩大,会继承解说state的更新历程。
updateComponent
/**
* Perform an update to a mounted component. The componentWillReceiveProps and
* shouldComponentUpdate methods are called, then (assuming the update isn't
* skipped) the remaining update lifecycle methods are called and the DOM
* representation is updated.
*
* By default, this implements React's rendering and reconciliation algorithm.
* Sophisticated clients may wish to override this.
*
* @param {ReactReconcileTransaction} transaction
* @param {ReactElement} prevParentElement
* @param {ReactElement} nextParentElement
* @internal
* @overridable
*/
updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
var inst = this._instance;
var willReceive = false;
var nextContext;
var nextProps;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
// Distinguish between a props update versus a simple state update
if (prevParentElement === nextParentElement) {
// Skip checking prop types again -- we don't read inst.props to avoid
// warning for DOM component props in this upgrade
nextProps = nextParentElement.props;
} else {
nextProps = this._processProps(nextParentElement.props);
willReceive = true;
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
inst.componentWillReceiveProps(nextProps, nextContext);
}
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext);
if ("development" !== 'production') {
"development" !== 'production' ? warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : void 0;
}
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
}
跳过除了state的其他源码部份,我们能够看到该要领中依然嵌套了一段对state的更新要领,这个要领就是state更新的尽头_processPendingState
。
_processPendingState
为何对state中的统一属性做屡次setState处置惩罚,不会获得屡次更新?比方
this.setState({ count: count++ });
this.set
那是由于源码中的多个nextState的更新,只做了一次assign操纵,以下源码请检察:
_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 = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
}
有人说,React笼统来讲,就是一个公式
UI=f(state).
确实云云,一个简朴的setState实行历程,内部潜伏了这么深的玄机,阅历多个模块的处置惩罚,阅历多个毛病处置惩罚机制以及对数据边境的推断,保证了一次更新的一般举行。同时我们也发明了为何setState的操纵不能简朴的说作是一个异步操纵,人人应该在文章中已找到了答案。
对其他react深层的明白,感兴致的同砚能够多多参考下源码。本文参考react源码版本为15.0.1
。