先来几个例子热热身:
......... 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