上次,我们谈到了setState的批量更新策略,还记得上次说的transcation.perform的执行流程以及批量更新的transaction的两个wrapper FLUSH_BATCHED_UPDATES 和RESET_BATCHED_UPDATES吗?
再次放上transaction的这张图
* wrappers (injected at creation time) * + + * | | * +-----------------|--------|--------------+ * | v | | * | +---------------+ | | * | +--| wrapper1 |---|----+ | * | | +---------------+ v | | * | | +-------------+ | | * | | +----| wrapper2 |--------+ | * | | | +-------------+ | | | * | | | | | | * | v v v v | wrapper * | +---+ +---+ +---------+ +---+ +---+ | invariants * perform(anyMethod) | | | | | | | | | | | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> * | | | | | | | | | | | | * | | | | | | | | | | | | * | | | | | | | | | | | | * | +---+ +---+ +---------+ +---+ +---+ | * | initialize close | * +-----------------------------------------+
经过上篇的分析,我们了解到setState的执行会将component放入dirtyComponents中,perform执行完anyMethod后会依次执行wrapper的close方法。真正的state更新会在perfrom调用的FLUSH_BATCHED_UPDATES.close里去实现。那FLUSH_BATCHED_UPDATES.close又是怎么实现更新的?带着这个疑问,我们继续读下去
还是依照调用栈的顺序,挨个看函数
调用栈
close
FLUSH_BATCHED_UPDATES的close函数
在flushBatchedUpdates处打上断点
发现调用栈如下
flushBatchedUpdates
这个方法在ReactUpdates.js 文件中。这个文件也有一个自己的transaction
|
|
注意到这个transaction重写了perform方法,它的perform在执行的时候,会调用reconcileTransaction.perform方法。也就是真正的方法被包了2个transaction的wrappers:
// perform 过程 ReactUpdatesFlushTransaction.wrappers的initialize | V reconcileTransaction.wrappers的initialize | V anyMethod | V reconcileTransaction.wrappers的close | V ReactUpdatesFlushTransaction.wrappers的close
看一下ReactUpdatesFlushTransaction的wrappers
再看一下flushBatchedUpdates的代码
来理一遍transaction的perform的执行顺序
- NESTED_UPDATES.initialize
- 记录dirtyComponents的长度
- UPDATE_QUEUEING.initialize
- 重置回调队列
- 执行reconcileTransaction.perform 包裹的 runBatchedUpdates
- NESTED_UPDATES.close
- 如果有新的dirtyComponents产生,调用flushBatchedUpdates;如果没有,重置dirtyComponents
- UPDATE_QUEUEING.close
- 执行回调队列里的所有函数
看到,这里的第5步,有没有想到什么?上一篇说到的setState的第二个函数的执行,就是在这个阶段执行的。也就是等transaction的最后,才会执行这里的回调,而不是变更一个就执行一个。
runBatchedUpdates
|
|
看到runBatchedUpdates做的一些事
- 对dirtyComponents做排序,让父组件在前面
- updateBatchNumber++ 更新批量更新的次数
- 遍历dirtyComponents
- 进行更新
- 有回调,就加入到回调队列
看到这,或许你跟我有一样的疑问
- 为什么要先处理父组件?
- 为什么要记录批量更新的次数?
别着急,这些我们先放一放,先去看看更新里做了什么
performUpdateIfNecessary
|
|
会检查component的批次是否和当前一致。
这里的performUpdateIfNecessary, 又调用了component.performUpdateIfNecessary
ReactCompositeComponent 的 performUpdateIfNecessary
|
|
之前的要更新的state都存在_pendingStateQueue里,这里会检测是否有待更新的状态,如果有,则执行updateComponent
updateComponent
终于走到更新组件这一步了
代码很长,但是不用太在意。
关注一下下面几点:
这个方法中间调用了一些更新阶段生命周期方法
componentWillReceiveProps和shouldComponetUpdate。把component的
_updateBatchNumber重置为null计算下个state
123456789101112131415161718192021222324252627// updateComponent 调用var nextState = this._processPendingState(nextProps, nextContext);// _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 = _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;},
这里会依次遍历_pendingStateQueue中的元素,将它们合并到nextState里。如果队列是函数,则传入上个nextState,将函数执行的结果作为一个state进行合并。到这,你有没有想起什么?上一篇,我们提到的获取新的state的方式的第一种,就是setState第一个参数为函数的方式,它能拿新的state原理就在这。
- 更新组件状态的方法1234567891011121314151617181920212223242526272829303132333435363738394041424344454647// updateComponent 调用this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);// _performComponentUpdate_performComponentUpdate: function (nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) {var _this2 = this;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) {if (process.env.NODE_ENV !== 'production') {measureLifeCyclePerf(function () {return inst.componentWillUpdate(nextProps, nextState, nextContext);}, this._debugID, 'componentWillUpdate');} else {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) {if (process.env.NODE_ENV !== 'production') {transaction.getReactMountReady().enqueue(function () {measureLifeCyclePerf(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), _this2._debugID, 'componentDidUpdate');});} else {transaction.getReactMountReady().enqueue(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst);}}},
这里做的事情
- 执行componentWillUpdate
- 更新props,state
- 进行渲染_updateRenderedComponent
- 执行componentDidUpdate
对于怎么渲染更新的组件,这里不往下看了
到这我们总算找到了flushBatchedUpdate主要做的事updateComponent了
总结
做了这么多事,其实就得到了下面一条信息:
ReactDefaultBatchingStrategy中的transaction会在close阶段,处理dirtyComponnets,更新它们的props,state及更新视图。
到这,state更新的过程也就结束了。
我们在回过头,想想之前遇到的问题
Q & A
同一批次相同组件的处理
Q: 会重复更新吗?
A:不会,每个component在加入dirtyComponents时,会标记component的_updateBatchNumber为当前批次,批量更新时,会检查这个批次是否是当前需要处理的批次,不是就不会更新了。假设dirtyComponents有两个相同的component,A1和A2,遍历执行更新,发现A1的批次和当前一致,会更新,然后把A1组件_updateBatchNumber置为null。当遍历到A2时,因为批次(被置为null了)不匹配,就不会更新。
为什么要对dirtyComponents排序
因为父组件的更新通常会造成子组件的更新,为了避免重复的更新工作,对dirtyComponents排序,先更新父组件,这样父组件更新的过程中,可以一道把子组件更新了。
执行过程中是否可能有新的state产生,如何处理
有可能的,比如父组件变更导致子组件的一些setState(比如在willReceiveComponent里),它会加入到dirtyComponents中,但是批次不同,等这批次更新完后(transaction没有结束),再次进行flushBatchedUpdates()进行更新
批次计数有何作用
避免重复的更新,做一个标志,表示更没更新