React setState源碼實現理解

<!-- TOC -->html

<!-- /TOC -->segmentfault

Q1

setState改變狀態以後,不會當即更新state值。因此,若是改變state值,react是何時進行組件的更新呢?setState()到底作了一些什麼呢?數組

A1

1. react生命週期

react生命週期

2. react更新state具體作了什麼

引入一段源碼app

react中定義的setState方法,定義了兩個參數(partialState,callback)。函數

partialState: 新的state值;
callback: 回調函數。post

setState

getInternalInstanceReadyForUpdate方法的目的是獲取當前組件對象,將其賦值給internalInstance變量。接下來判斷當前組件對象的state更新隊列是否存在,若是存在則將partialState也就是新的state值加入隊列;若是不存在,則建立該對象的更新隊列。而後進入enqueueUpdate方法。this

enqueueSetState

enqueueCallback也是先獲取當前組件對象,若是已經存在其餘回調,就加入等待回調隊列,若是當前沒有回調,就建立等待回調隊列。而後進入enqueueUpdate方法。spa

能夠發現,enqueueSetState&enqueueCallback最終都是進入enqueueUpdate方法。下面咱們來看看enqueueUpdate方法。調試

enqueueCallback

官方註解是:給組件作個標記:須要從新渲染,而且將可選的回調函數添加到函數列表中,這些函數將在從新渲染的時候執行。

咱們看一下函數具體作了哪些事。發現這個函數只是作了一個判斷:若是batchingStrategy.isBatchingUpdates爲false,就執行batchingStrategy.batchedUpdates(enqueueUpdate,component),不然就加入dirtyComponents。

這裏提到batchingStrategy,批量更新策略。

enqueueUpdate

批量更新策略是什麼呢?看代碼發現batchingStrategy批量更新策略只是一個簡單的對象,定義了一個 isBatchingUpdates 的布爾值和一個 batchedUpdates 方法。默認isBatchingUpdates(下面稱爲更新標誌)爲false,而後會進入batchedUpdates方法,先把更新標誌isBatchingUpdates設爲true,而後執行transaction.perform(callback),即transaction.perform(enqueueUpdate)。

React內部採用了"狀態機"的概念,組件處於不一樣的狀態時,所執行的邏輯也並不相同。以組件更新流程爲例,React以事務+狀態的形式對組件進行更新。

batching

經過上面的一部分代碼,咱們發現setState()方法主要是enqueueUpdate()進行狀態更新,怎樣進行狀態更新呢?定義了一個批量更新策略:判斷更新標誌isBatchingUpdates的值,若是爲false,調用batchedUpdates()-->(先把更新標誌isBatchingUpdates改成true,而後調用transaction.perform(enqueueUpdate))。若是爲true,就把組件加入dirtyComponents數組中。

###2

React內部採用了"狀態機"的概念,組件處於不一樣的狀態時,所執行的邏輯也並不相同。以組件更新流程爲例,React以事務+狀態的形式對組件進行更新,所以接下來咱們看看事務的機制。

3. transaction 事務

wrappers (injected at creation time)
                                   +        +
                                   |        |
                 +-----------------|--------|--------------+
                 |                 v        |              |
                 |      +---------------+   |              |
                 |   +--|    wrapper1   |---|----+         |
                 |   |  +---------------+   v    |         |
                 |   |          +-------------+  |         |
                 |   |     +----|   wrapper2  |--------+   |
                 |   |     |    +-------------+  |     |   |
                 |   |     |                     |     |   |
                 |   v     v                     v     v   | wrapper
                 | +---+ +---+   +---------+   +---+ +---+ | invariants
perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
+----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
                 | |   | |   |   |         |   |   | |   | |
                 | |   | |   |   |         |   |   | |   | |
                 | |   | |   |   |         |   |   | |   | |
                 | +---+ +---+   +---------+   +---+ +---+ |
                 |  initialize                    close    |
                 +-----------------------------------------+

這是官方代碼的解析圖。

能夠看出調用函數是perform(anyMethod),而後方法anyMethod被wrapper包裹了,wrapper依次執行了initialize->anyMethod->close

function anyMethod(){
    console.log('xx')
};
transaction.perform(anyMethod);

代碼的執行順序是

initialize()
輸出xx
close()

因此這裏wrapper是怎樣定義的呢?

wrapper

第二個wrapper比較簡單,先來看一下第二個wrapper。

第二個wrapper(RESET_BATCHED_UPDATES)的做用是將更新標誌isBatchingUpdates重置爲false;個人理解這裏是收集完全部要更新的state值,都加入_pendingStateQueue待更新狀態隊列了,而後組件更新完了以後,將更新標誌重置爲false,等待下次更新。而後下面來看一下第一個wrapper。

wwrapper2
5&f=png&s=54081)

第一個wrapper主要的做用是更新組件,執行了ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)。

wrapper2

runBatchedUpdates

updateIfNecessary

能夠看到flushBatchedUpdates方法循環遍歷全部的dirtyComponents,又經過事務的形式調用runBatchedUpdates方法。

一共作了兩件事:

  • 一是經過執行updateComponent方法來更新組件
  • 二是若setState方法傳入了回調函數則將回調函數存入callbackQueue隊列。

而後看一下updateComponent方法,官方註釋是:更新組件,會調用shouldComponentUpdate,而後調用剩餘的生命週期函數,更新DOM結構

annotation

updateComponent

pendingstate

這裏終於更新了組件。看代碼會發如今shouldComponentUpdate以前,執行了_processPendingState方法,該方法主要對state進行處理:

  • 1.若是更新隊列爲null,那麼返回原來的state;
  • 2.若是更新隊列有一個更新,那麼返回更新值;
  • 3.若是更新隊列有多個更新,那麼經過for循環將它們合併;

綜上說明了,在一個生命週期內,在componentShouldUpdate執行以前,全部的state變化都會被合併,最後統一處理。

wrapper2s

4. 回顧上述問題

綜上,

  • setState()爲啥沒有當即更新this.state值呢
  • 若是在componentDidMount()中連續屢次setState,沒法進行state累加呢
  • 批量更新策略isBatchingStrategy幹了什麼,怎麼作到更新的呢

那按照上述說的批量更新,第一次setState-->進入enqueueUpdate()-->此時isBatchingUpdates默認爲false-->batchedUpdates(enqueueUpdate,...)-->設置isBatchingUpdates爲true;transaction.perform(enqueueUpdates);-->(第一個wrapper:FLUSH_BATCHED_UPDATES)組件更新-->(第二個wrapper:RESET_BATCHED_UPDATES的close方法)設置isBatchingUpdates爲false-->第二次setState-->isBatchingUpdates爲false-->..-->組件更新-->isBatchingUpdates恢復爲false。

這樣和結果不對呀?按上述邏輯的話,豈不是每次setState都會更新this.state的值?

調試代碼會發現,原來整個將 React 組件渲染到 DOM 中的過程就處於一個大的 Transaction 中。

result

在進入生命週期以前,就會調用batchedUpdates(),因此此時isBatchingUpdates已經修改成true了。後面第一次進入setState()時,就會進入加入dirtyComponent中。因此這也就是爲何兩次打印 this.state.foods 都是 '' 的緣由,新的 state 尚未被應用到組件中。

5. 總結

  • setState(partialState, callback),不會當即更新state值,要合併全部的state變化後,而後從新渲染的時候,state值纔會更新。
  • setState(partialState, callback): callback會在全部狀態更新以後再調用(demo中state的foods&drinks所有更新以後纔會調用)
  • 事務這麼有用,那咱們能夠調用事務嗎?答案是不能夠。
  • 另外在componentWillMount裏面setState()不會觸發從新渲染

willMount

Q2

在render函數裏,沒法setState

A2

在render函數中不能setState()。

從react生命週期能夠看出:state更新會從新觸發render(),因此會致使setState()-->re-render()-->setState()--re-render()-->...-->setState()-->re-render(),一直循環往復。

react生命週期

因此,同理在state更新的生命週期的函數中(componentWillUpdate/componentDidUpdate),都不能setState()

參考資料

https://juejin.im/post/59cc4c...

https://zh-hans.reactjs.org/d...

https://www.imooc.com/article...

https://segmentfault.com/a/11...

相關文章
相關標籤/搜索