在前兩篇文章中,咱們分析了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