在前两篇文章中,咱们分析了React组件的实现,挂载以及生命周期的流程。在阅读源码的过程当中,咱们常常会看到诸如transaction
和UpdateQueue
这样的代码,这涉及到React中的两个概念:事务和更新队列。由于以前的文章对于这些咱们一笔带过,因此本篇咱们基于你们都再熟悉不过的setState
方法来探究事务机制和更新队列。javascript
在第一篇文章《React源码解析(一):组件的实现与挂载》中咱们已经知道,经过class
声明的组件原型具备setState
方法:java
该方法传入两个参数partialState
和callBack
,前者是新的state值,后者是回调函数。而updater
是在构造函数中进行定义的:数组
能够看出updater
是构造函数传入的,因此找到哪里执行了new ReactComponent
,就能找到updater
是什么。以自定义组件ReactCompositeComponent
为例,在_constructComponentWithoutOwner
方法中,咱们发现了它的踪影:浏览器
return new Component(publicProps, publicContext, updateQueue);
复制代码
对应参数发现updater
其实就是updateQueue
。接下来咱们看看this.updater.enqueueSetState
中的enqueueSetState
是什么:app
getInternalInstanceReadyForUpdate
方法的目的是获取当前组件对象,将其赋值给internalInstance
变量。接下来判断当前组件对象的state更新队列是否存在,若是存在则将partialState
也就是新的state值加入队列;若是不存在,则建立该对象的更新队列,能够注意到队列是以数组形式存在的。咱们看下最后调用的enqueueUpdate
方法作了哪些事: 函数
由代码可见,当batchingStrategy.isBatchingUpdates
为false
时,将执行batchedUpdates
更新队列,若为true
时,则将组件放入dirtyComponent
中。咱们看下batchingStrategy
的源码:post
大体地看下,isBatchingUpdates的初始值是false
,且batchedUpdates
内部执行传入的回调函数。性能
看到这么长的逻辑彷佛有点懵,但从这些代码咱们隐约意识到React并非随随便便就进行组件的更新,而是经过状态(好像是true/false)的判断来执行。实际上,React内部采用了"状态机"的概念,组件处于不一样的状态时,所执行的逻辑也并不相同。以组件更新流程为例,React以事务+状态的形式对组件进行更新,所以接下来咱们探讨事务的机制。ui
首先看下官方源码的解析图:this
<pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
复制代码
从流程图上看很简单,每个方法会被wrapper
所包裹,必须用perform
调用,在被包裹方法先后分别执行initialize
和close
。举例说明普通函数和被wrapper
包裹的函数执行时有什么不一样:
function method(){
console.log('111')
};
transaction.perform(method);
//执行initialize方法
//输出'111'
//执行close方法
复制代码
咱们知道在前面的batchingStrategy
的代码中transaction.perform(callBack)
实际调用的是transaction.perform(enqueueUpdate)
,但enqueueUpdate
方法中仍然存在transaction.perform(enqueueUpdate)
,这样岂不是形成了死循环?
为了不可能死循环的问题,wrapper
的做用就显现出来了。咱们看下这两个wrapper
是如何定义的:
从上面的思惟导图可知,isBatchingUpdates
初始值为false
,当以事务的形式执行transaction.perform(enqueueUpdate)
时,实际上执行流程以下:
// RESET_BATCHED_UPDATES.initialize() 实际为空函数
// enqueue()
// RESET_BATCHED_UPDATES.close()
复制代码
用流程图来讲明:
用文字说明的话,那就是RESET_BATCHED_UPDATES
这个wrapper
的做用是设置isBatchingUpdates
也就是组件更新状态的值,组件有更新要求的话则设置为更新状态,更新结束后从新恢复原状态。
这样作有什么好处呢?固然是为了不组件的重复render,提高性能啦~
RESET_BATCHED_UPDATES
是用于更改isBatchingUpdates
的布尔值false
或者true
,那FLUSH_BATCHED_UPDATES
的做用是什么呢?其实能够大体猜到它的做用是更新组件,先看下FLUSH_BATCHED_UPDATES.close()
的实现逻辑:
能够看到flushBatchedUpdates
方法循环遍历全部的dirtyComponents
,又经过事务的形式调用runBatchedUpdates
方法,由于源码较长因此在这里直接说明该方法所作的两件事:
updateComponent
方法来更新组件setState
方法传入了回调函数则将回调函数存入callbackQueue
队列。看下updateComponent
源码:
能够看到执行了componentWillReceiveProps
方法和shouldComponentUpdate
方法。其中不能忽视的一点是在shouldComponentUpdate
以前,执行了_processPendingState
方法,咱们看下这个函数作了什么:
该函数主要对state
进行处理:
1.若是更新队列为null
,那么返回原来的state
;
2.若是更新队列有一个更新,那么返回更新值;
3.若是更新队列有多个更新,那么经过for循环将它们合并;
综上说明了,在一个生命周期内,在componentShouldUpdate
执行以前,全部的state
变化都会被合并,最后统一处理。
回到_updateComponent
,最后若是shouldUpdate
为true
,执行_performComponentUpdate
方法:
大体浏览下会发现仍是一样的套路,执行componentWillUpdate
生命周期方法,更新完成后执行componentDidUpdate
方法。咱们看下负责更新的_updateRenderedComponent
方法:
这段代码的思路就很清晰了:
shouldUpdateReactComponent
是一个方法(下文简称should
函数),根据传入的新旧组件信息判断是否进行更新。should
函数返回true
,执行旧组件的更新。should
函数返回false
,执行旧组件的卸载和新组件的挂载。结合前面的流程图,咱们对整个组件更新流程进行补充:
setState
回调函数setState
回调函数与state
的流程类似,state
由enqueueSetState
处理,回调函数由enqueueCallback
处理,感兴趣的读者能够自行探究。
setState
致使的崩溃问题咱们已经知道,this.setState
实际调用了enqueueSetState
,在组件更新时,由于新的state
还未进行合并处理,故在下面performUpdateIfNecessary
代码中this._pendingStateQueue
为true
:
而合并state
后React会会将this._pendingStateQueue
设置为null
,这样dirtyComponent
进入下一次批量处理时,已经更新过的组件不会进入重复的流程,保证组件只作一次更新操做。
因此不能在componentWillUpdate
中调用setState
的缘由,就是setState
会令_pendingStateQueue
为true
,致使再次执行updateComponent
,然后会再次调用componentWillUpdate
,最终循环调用componentWillUpdate
致使浏览器的崩溃。
咱们在以前的代码中,对于更新队列的标志batchingStrategy
,咱们直接转向对ReactDefaultBatchingStrategy
进行分析,这是由于React内部存在大量的依赖注入。在React初始化时,ReactDefaultInjection.js
注入到ReactUpdates
中做为默认的strategy。依赖注入在React的服务端渲染中有大量的应用,有兴趣的同窗能够自行探索。
回顾:
《React源码解析(一):组件的实现与挂载》
《React源码解析(二):组件的生命周期》
《React源码解析(四):事件系统》 联系邮箱:ssssyoki@foxmail.com