先来几个例子热热身:
.........
constructor(props){
super(props);
this.state = {
index: 0
}
}
componentDidMount() {
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
setTimeout(() => {
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
}, 400)
}
.....
上面打印出来是什么?
这个例子网上比较多,基本上详解setState机制的用的都是这个,正解是0,0,2,3
一个例子一般满足不了我,于是我就多试了几个,再看下一个:
componentDidMount() {
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
setTimeout(() => {
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
}, 400)
}
这个打印出来是什么?正解是0,2,3,再来一个
componentDidMount() {
this.setState({
index: this.state.index + 1
});
console.log(this.state.index);
}
这个打印出来是什么?你是不是突然自信,这肯定是1了吧,实际上打印出来还是0,mmp,
还有很多问题,比如为什么setState会触发更新,为什么有些生命周期中不能调用setState,或者调用了没效果,setState为什么是异步的,等等问题,可能我们都知道答案,但是却不知道为什么,如果仅仅是为了开发需要,那么你记住答案就行了,但是如果想真正理解它,就必须挖它的源码!
OK,直接来看源码(以下源码均为React v15.0.0中的,可上GitHub查看,我只贴部分用到的)
ReactComponent.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.'
);
if (__DEV__) {
ReactInstrumentation.debugTool.onSetState();
warning(
partialState != null,
'setState(...): You passed an undefined or null state object; ' +
'instead, use forceUpdate().'
);
}
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
setState函数允许接受两个参数,第一个是partialState,它可以是一个Object,也可以是一个function,也可以是一个空对象指针null,(这里的invariant是一个库,它的用法就是如果不满足第一个参数,则打印后面的错误信息,通常是开发环境中使用),中间部分pass,直接看底部,会发现有两个函数,enqueueSetState和enqueueCallback,enqueueSetState传两个参数,一个是this,还有一个是partialState,就是我们传递进去的setState第一个对象参数;而enqueueCallback是在setState如果有第二个参数callback的时候才会去执行,传入当前组件对象、callback函数和'setState'。我在控制台进行了断点调试,看一下这里传给这两个函数的this指什么:
可以看出这里的this就是当前所在的App组件,可以看到它的原型__proto__就是ReactComponent。
那么这两个函数定义是什么呢?再看下面的源码
enqueueSetState: function(publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState'
);
if (!internalInstance) {
return;
}
var queue =
internalInstance._pendingStateQueue ||
(internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
},
..........
enqueueCallback: function(publicInstance, callback, callerName) {
ReactUpdateQueue.validateCallback(callback, callerName);
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
// Previously we would throw an error if we didn't have an internal
// instance. Since we want to make it a no-op instead, we mirror the same
// behavior we have in other enqueue* methods.
// We also need to ignore callbacks in componentWillMount. See
// enqueueUpdates.
if (!internalInstance) {
return null;
}
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
// TODO: The callback here is ignored when setState is called from
// componentWillMount. Either fix it or disallow doing so completely in
// favor of getInitialState. Alternatively, we can disallow
// componentWillMount during server-side rendering.
enqueueUpdate(internalInstance);
},
可以注意到两个函数开始都调用了getInternalInstanceReadyForUpdate(获得内部实例用来准备更新)这个函数,那就先来看一下该函数的定义:
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
var internalInstance = ReactInstanceMap.get(publicInstance);
if (!internalInstance) {
if (__DEV__) {
// Only warn when we have a callerName. Otherwise we should be silent.
// We're probably calling from enqueueCallback. We don't want to warn
// there because we already warned for the corresponding lifecycle method.
warning(
!callerName,
'%s(...): Can only update a mounted or mounting component. ' +
'This usually means you called %s() on an unmounted component. ' +
'This is a no-op. Please check the code for the %s component.',
callerName,
callerName,
publicInstance.constructor.displayName
);
}
return null;
}
if (__DEV__) {
warning(
ReactCurrentOwner.current == null,
'%s(...): Cannot update during an existing state transition (such as ' +
'within `render` or another component\'s constructor). Render methods ' +
'should be a pure function of props and state; constructor ' +
'side-effects are an anti-pattern, but can be moved to ' +
'`componentWillMount`.',
callerName
);
}
return internalInstance;
}
该函数刚开始就先把我们的ReactComponent实例App组件传入了一个ReactInstanceMap(React实例map)对象的get方法中,那我们再来看一下这个对象的定义:
var ReactInstanceMap = {
/**
* This API should be called `delete` but we'd have to make sure to always
* transform these to strings for IE support. When this transform is fully
* supported we can rename it.
*/
remove: function(key) {
key._reactInternalInstance = undefined;
},
get: function(key) {
return key._reactInternalInstance;
},
has: function(key) {
return key._reactInternalInstance !== undefined;
},
set: function(key, value) {
key._reactInternalInstance = value;
},
};
module.exports = ReactInstanceMap;
是不是已经有点晕了,这里我虽然不大清楚这个对象是啥,但是从它的四个方法(增删改查)能看出,应该是用来存储某些对象,用key/value的形式,不过其实文档注释里有提到了,其实该数据结构类似于es6的Map,这个我们熟悉的,就是键值对的集合,而在getInternalInstanceReadyForUpdate中直接是用了ReactInstanceMap.get(publicInstance),说明在setState之前应该是已经执行过了ReactInstanceMap.set(publiceInstance),我们再猜想一下,这里既然是get的是ReactComponent这个key,那么可能在组件mount的时候将它set进了这个ReactInstanceMap对象中去了,是否是这样呢,这个后面探讨React生命周期的一些源码的时候再去讨论,此时我们暂时不管那些,我还是把它断点出来看看这个取出的实例对象是什么:
可以看到这是一个ReactCompositeComponentWrapper,这是个啥玩意儿,不太清楚,先不管它,只要知道这个internalInstance是从这个ReactInstanceMap对象中取出来的就行了,而传入的key就是我们当前的实例组件App。
回到我们的enqueueSetState函数,第一步就是取出这个internalInstance,如果不存在,就返回,如果存在,就var queue = internalInstance对象上的一个_pendingStateQueue(等待状态队列)属性,如果该属性不存在,就新建一个该属性为空数组,因此可以看出_pendingStateQueue就是一个数组对象,其实再看一下上图可以看到刚开始的_pendingStateQueue是一个null。然后将我们传进去的partialState推进数组,然后执行enqueueUpdate方法,并传入internalInstance对象。这个_pendingStateQueue是什么,这里暂时先不管它,从字面意思可以看出应该是一个等待的状态队列,我们把要改的paritialState塞入了这个等待队列中。
接下来看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);
}
这个函数关键点在于batchingStrategy(批量策略)对象的isBatchingUpdates是否为true,根据字面意思就是当前并不在批量更新的时候,那么就进行批量更新,我们先来看一下这个batchingStrategy是什么:
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);
}
},
};
module.exports = ReactDefaultBatchingStrategy;
可以看到默认情况下这个isBatchingUpdates是false,表示不在批量更新,来看一下这个batchedUpdates方法,先获取此时isBatchingUpdates的状态,然后将isBatchingUpdates改为true,表示当前正在批量更新,如果刚刚获取的状态为true,就会又回到刚刚的enqueueUpdate方法,然后走下面的dirtyComponents部分,退出if条件句。如果确实不处在批量更新状态下,则执行transaction.perform()方法,这边我先暂停一下,这里就可以看出React对于重渲染做的应对策略,就是先设立一个状态表示当前是否正在批量更新,如果不是,那就可以批量更新,然后更新view重渲染,而进行批量更新的时候,首先就先把这个标志状态改为了正在批量更新,导致后面如果有其他的批量更新要处理,那就不能进入,只能传到dirtyComponents里面去,就好比说上公共厕所时候门口会有一个标志有人/无人一样,一个道理,永远不可能两个人或多个人一起上同一个厕所。那么何为transaction呢?其实就在同一个文件的上面就已经定义了,来看:
var ReactUpdates = require('ReactUpdates');
var Transaction = require('Transaction');
var emptyFunction = require('emptyFunction');
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();
可以发现这里引入了一个Transaction库,也是一个非常重要的概念,那我们先来看一下这个库,然后再回过头去看这个transaction实例是什么:
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Transaction
*/
'use strict';
var invariant = require('invariant');
/**
* `Transaction` creates a black box that is able to wrap any method such that
* certain invariants are maintained before and after the method is invoked
* (Even if an exception is thrown while invoking the wrapped method). Whoever
* instantiates a transaction can provide enforcers of the invariants at
* creation time. The `Transaction` class itself will supply one additional
* automatic invariant for you - the invariant that any transaction instance
* should not be run while it is already being run. You would typically create a
* single instance of a `Transaction` for reuse multiple times, that potentially
* is used to wrap several different methods. Wrappers are extremely simple -
* they only require implementing two methods.
*
* <pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
*
* Use cases:
* - Preserving the input selection ranges before/after reconciliation.
* Restoring selection even in the event of an unexpected error.
* - Deactivating events while rearranging the DOM, preventing blurs/focuses,
* while guaranteeing that afterwards, the event system is reactivated.
* - Flushing a queue of collected DOM mutations to the main UI thread after a
* reconciliation takes place in a worker thread.
* - Invoking any collected `componentDidUpdate` callbacks after rendering new
* content.
* - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
* to preserve the `scrollTop` (an automatic scroll aware DOM).
* - (Future use case): Layout calculations before and after DOM updates.
*
* Transactional plugin API:
* - A module that has an `initialize` method that returns any precomputation.
* - and a `close` method that accepts the precomputation. `close` is invoked
* when the wrapped process is completed, or has failed.
*
* @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
* that implement `initialize` and `close`.
* @return {Transaction} Single transaction for reuse in thread.
*
* @class Transaction
*/
var Mixin = {
/**
* Sets up this instance so that it is prepared for collecting metrics. Does
* so such that this setup method may be used on an instance that is already
* initialized, in a way that does not consume additional memory upon reuse.
* That can be useful if you decide to make your subclass of this mixin a
* "PooledClass".
*/
reinitializeTransaction: function() {
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
_isInTransaction: false,
/**
* @abstract
* @return {Array<TransactionWrapper>} Array of transaction wrappers.
*/
getTransactionWrappers: null,
isInTransaction: function() {
return !!this._isInTransaction;
},
/**
* 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) {
invariant(
!this.isInTransaction(),
'Transaction.perform(...): Cannot initialize a transaction when there ' +
'is already an outstanding transaction.'
);
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;
},
initializeAll: function(startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
// Catching errors makes debugging more difficult, so we start with the
// OBSERVED_ERROR state before overwriting it with the real return value
// of initialize -- if it's still set to OBSERVED_ERROR in the finally
// block, it means wrapper.initialize threw.
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ?
wrapper.initialize.call(this) :
null;
} finally {
if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {
// The initializer for wrapper i threw an error; initialize the
// remaining wrappers but silence any exceptions from them to ensure
// that the first error is the one to bubble up.
try {
this.initializeAll(i + 1);
} catch (err) {
}
}
}
}
},
/**
* 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) {
invariant(
this.isInTransaction(),
'Transaction.closeAll(): Cannot close transaction when none are open.'
);
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;
},
};
var Transaction = {
Mixin: Mixin,
/**
* Token to look for to determine if an error occurred.
*/
OBSERVED_ERROR: {},
};
module.exports = Transaction;
不要看这段代码这么长,实际上很多都是注释,别被吓到了-。-!注释开头给了个图,大概解释一下执行perform的流程:perform这个方法接收一些method参数,然后有一些wrappers出现了,wrappers的initialize开始执行,执行完后,method开始执行,然后wrappers的close开始执行,执行完后结束流程,有几个要注意,wrappers是在创建的时候就已经注入了,并且根据wrapper的顺序,先执行initialize的先执行close。说到这差不多能大概明白这个库的perform的作用了:给传入的method执行前后添加一些钩子函数。method还是照常执行,只不过在执行的前后会先执行一些别的函数,具体流程如下:
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);
}
}
};
我们发现执行一个while循环,循环通过transaction.perform来执行runBatchedUpdates,这个transaction实例的钩子函数有哪些,这个我没有深入去看(其实后来还是去看了一下,确实是清空了dirtyComponents),因为我看到了上面注释第一行提到了它的wrappers的功能,是清空这个dirtyComponents,这个dirtyComponents是不是有点眼熟,就是我们刚刚在enqueueUpdate那个函数中,当此时是批量更新时,我们会将传入的component(也就是那个internalInstance,即ReactCompositeComponentWrapper)推入dirtyComponents中去,这个dirtyComponents是什么,暂时我们只要知道它是一个数组就行了,继续刚刚的,我们来看一下这个runBatchedUpdates函数的定义:
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
invariant(
len === dirtyComponents.length,
'Expected flush transaction\'s stored dirty-components length (%s) to ' +
'match dirty-components array length (%s).',
len,
dirtyComponents.length
);
// 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()
);
}
}
}
}
这段源码有点复杂,我来把关键点提出来看一下:
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
ReactReconciler.performUpdateIfNecessary(
component,
transaction.reconcileTransaction
);
}
}
是不是简单许多?我们对dirtyComponents进行了一个循环,并且把里面每一个组件都传给了一个ReactReconciler.performUpdateIfNecessary(如果有必要的话执行更新)这个方法,那我们再来看一下这个方法的定义,注意,这里是ReactReconciler(React调和器)这个对象中的方法,不要搞错了,因为React整个项目中同名的方法有很多个,但是所属对象不同:
/**
* Flush any dirty changes in a component.
*
* @param {ReactComponent} internalInstance
* @param {ReactReconcileTransaction} transaction
* @internal
*/
performUpdateIfNecessary: function(
internalInstance,
transaction
) {
internalInstance.performUpdateIfNecessary(transaction);
if (__DEV__) {
ReactInstrumentation.debugTool.onUpdateComponent(internalInstance);
}
},
这边又出现了一个internalInstance参数,然后它也有一个同名的performUpdateIfNecessary方法,但是我们不知道这个方法是谁的方法,那我们看一下刚刚传入的是什么,就是那个dirtyComponents组件数组,也就是由那些ReactCompositeComponentWrapper组成的,直接来看ReactCompositeComponent.js中的源码:
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
);
}
},
receiveComponent最终会触发updateComponent,而updateComponent会刷新view,最终完成更新!
以上就是整个React内部setState的完整流程,那其实关于最初列举的几个例子,关键点就在于当前状态是否为批量更新,打印出来为0的,说明当前正处在批量更新,所以组件被推入进了dirtyComponents,但是当此时的批量更新结束时,Transaction的钩子函数close会把isBatchingUpdate改为false,所以当下一次setState时,会把之前推入dirtyComponents的所有组件重新遍历一遍,然后执行更新。至于为什么不能在getInitialState,componentWillMount, render,componentWillUpdate中使用setState,是因为mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了。
以上。
==============
2018.01.18上午更新:
今天我又发现一个奇怪的现象,看代码:
constructor(){
super();
this.state = {
index: 0
}
}
handleClick(){
this.setState({
index: this.state.index + 1
});
console.log(this.state.index)
}
<button onClick={this.handleClick}>{this.state.index}</button>
点击一下,打印出来什么?为什么还是0?!那就说明isBatchingUpdate又变成true了,到底谁在里面!!! 但是button里的text已经是1了啊,到底是谁把它更新了?行,那就断点调试一下:
果然是true,再看右边调用栈,果然有人用了batchUpdates,来看看是谁,一个叫dispatchEvent的家伙,我们来看看这是啥:
那就应该是事件里面的内容了,具体细节不大清楚,那可以知道应该是这个dispatchEvent先执行了batchUpdates,然后把isbatchingUpdate改为true,所以此时的事件点击没有能直接去更新,而是进入了dirtyComponent,而事件结束的时候,dispatchEvent的transaction的closeAll又遍历了dirtyComponent,所以执行更新,也就是说,整个状态遍历更新都是交给这个dispatchEvent来完成的,而并非由我们的事件直接操作,至于里面的细节,下回再讲!
1