React源碼解析(三):詳解事務與更新隊列

在前兩篇文章中,咱們分析了React組件的實現,掛載以及生命週期的流程。在閱讀源碼的過程當中,咱們常常會看到諸如transactionUpdateQueue這樣的代碼,這涉及到React中的兩個概念:事務和更新隊列。由於以前的文章對於這些咱們一筆帶過,因此本篇咱們基於你們都再熟悉不過的setState方法來探究事務機制和更新隊列。javascript

1.setState相關

在第一篇文章《React源碼解析(一):組件的實現與掛載》中咱們已經知道,經過class聲明的組件原型具備setState方法:java

該方法傳入兩個參數partialStatecallBack,前者是新的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.isBatchingUpdatesfalse時,將執行batchedUpdates更新隊列,若爲true時,則將組件放入dirtyComponent中。咱們看下batchingStrategy的源碼:post

大體地看下,isBatchingUpdates的初始值是false,且batchedUpdates內部執行傳入的回調函數。性能

看到這麼長的邏輯彷佛有點懵,但從這些代碼咱們隱約意識到React並非隨隨便便就進行組件的更新,而是經過狀態(好像是true/false)的判斷來執行。實際上,React內部採用了"狀態機"的概念,組件處於不一樣的狀態時,所執行的邏輯也並不相同。以組件更新流程爲例,React以事務+狀態的形式對組件進行更新,所以接下來咱們探討事務的機制。ui

2.transaction事務

首先看下官方源碼的解析圖: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調用,在被包裹方法先後分別執行initializeclose。舉例說明普通函數和被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,最後若是shouldUpdatetrue,執行_performComponentUpdate方法:

大體瀏覽下會發現仍是一樣的套路,執行componentWillUpdate生命週期方法,更新完成後執行componentDidUpdate方法。咱們看下負責更新的_updateRenderedComponent方法:

這段代碼的思路就很清晰了:

  1. 獲取舊的組件信息
  2. 獲取新的組件信息
  3. shouldUpdateReactComponent是一個方法(下文簡稱should函數),根據傳入的新舊組件信息判斷是否進行更新。
  4. should函數返回true,執行舊組件的更新。
  5. should函數返回false,執行舊組件的卸載和新組件的掛載。

結合前面的流程圖,咱們對整個組件更新流程進行補充:

4.寫在最後

(1)setState回調函數

setState回調函數與state的流程類似,stateenqueueSetState處理,回調函數由enqueueCallback處理,感興趣的讀者能夠自行探究。

(2)關於setState致使的崩潰問題

咱們已經知道,this.setState實際調用了enqueueSetState,在組件更新時,由於新的state還未進行合併處理,故在下面performUpdateIfNecessary代碼中this._pendingStateQueuetrue

而合併state後React會會將this._pendingStateQueue設置爲null,這樣dirtyComponent進入下一次批量處理時,已經更新過的組件不會進入重複的流程,保證組件只作一次更新操做。

因此不能在componentWillUpdate中調用setState的緣由,就是setState會令_pendingStateQueuetrue,致使再次執行updateComponent,然後會再次調用componentWillUpdate,最終循環調用componentWillUpdate致使瀏覽器的崩潰。

(3)關於React依賴注入

咱們在以前的代碼中,對於更新隊列的標誌batchingStrategy,咱們直接轉向對ReactDefaultBatchingStrategy進行分析,這是由於React內部存在大量的依賴注入。在React初始化時,ReactDefaultInjection.js注入到ReactUpdates中做爲默認的strategy。依賴注入在React的服務端渲染中有大量的應用,有興趣的同窗能夠自行探索。

回顧:
《React源碼解析(一):組件的實現與掛載》
《React源碼解析(二):組件的生命週期》
《React源碼解析(四):事件系統》 聯繫郵箱:ssssyoki@foxmail.com

相關文章
相關標籤/搜索