React淺談setState

寫到前面

爲何是setState,由於對於你們而言,大多數使用react的新手或者初學者,大多會直接接觸到setState,並且這個方法也多是接觸最多的操做方法。那麼要想詳細瞭解setState究竟在React中作了什麼事情,就須要深刻了解一下。而在最新的React 16版本中,React的核心渲染框架時進行過一次升級的,由以前的React升級到了React Fiber。(PS:本文針對菜鳥、初級工程師而寫,有錯誤不足之處,請各位大佬指出更正。感受太low,請繞道,謝謝。)前端

  • 爲何會升級?
  • 爲何瞭解Fiber?

彆着急,讓我來慢慢給大家解答,在16版本以前,React使用的仍是舊版的渲染核心,它的渲染過程是一口氣完成,怎麼理解呢?就是會一次性遍歷你全部的Dom節點,這個過程取決於你的應用的複雜程度。固然,這個過程通常比較快,可是也不排除在大型複雜應用中出現比較長的等待時間,這個時間是基於ms級別的。而做爲一個前端工程師,性能優化是比較重要的一方面之一,你們都知道,瀏覽器是的渲染引擎是單線程的,這就意味着一個時間段以內只能完成一件事。當你的應用過於複雜時,用戶操做變多,弊端就顯示出來了:卡頓,未響應,甚至是頁面崩潰...這就是爲何React會升級到React Fiber,在未升級以前,渲染模式是這樣的:react

假設你的結構是這樣的 A組件 => B組件 => C/D/E組件 D組件 => F組件 未使用Fiber架構的渲染方式git

他的舊版渲染模式是這樣的: github

以render()函數爲分界線。從頂層組件開始,一直往下,直至最底層子組件。而後再往上。組件update階段同理。一直執行,直到完成,這個過程徹底不理你。(我喜歡叫狗不理階段)

在升級爲Fiber以後,就如同游泳同樣,每一個一段時間,都須要上岸呼吸一口氣,因此渲染模式就變成更瞭如下狀況: 算法

潛水員會每隔一段時間就上岸,看是否有更重要的事情要作。數組

加入fiber的react將組件更新分爲兩個階段,Reconcile階段和Commit階段。瀏覽器

  • Reconcile階段,在這個階段內,React經過diff算法,判斷哪些組價須要更新,經須要更新的組件打上tag(標記),再將全部須要更新的組件添加到一個數組中,等待或者執行更新任務。注意:這個階段是能夠被打斷的,也就是說在這個階段內,react檢測到有用戶操做行爲,或者是其餘的一些事情都會打斷,在事件執行完畢以後在從新將進行此階段,是從新進行。
  • Commit階段,這個階段是根據Reconcile階段生成的更新的數組,遍歷更新DOM,這個階段是一次性執行完畢的,而且是不會被打斷的。

經過這個倆個階段,你就會明白,爲何以前會把componentWillMount、componentWillReviceProps和componentWillUpdate標記爲不安全的生命週期函數了,由於在Reconcile階段,被打斷以後是從新進行的,就有可能形成對此的數據請求,對此渲染,形成沒必要要的資源、性能浪費(這裏有一個比較有意思飢餓問題,聰明的同窗應該已經猜出來了,react如今尚未公佈解決方法哦)。安全

Fiber具體是什麼樣的?

Fiber實際上是一個對象。在Fiber源碼中,有這麼一段描述性能優化

A Fiber is work on a Component that needs to be done or was done. There can be more than one per component.bash

Fiber就是經過對象記錄組件上須要作或者已經完成的更新,一個組件能夠對應多個Fiber。

接下來讓咱們看看Fiber具體是什麼樣子的?既然是一個對象,就確定是{}模式。以下:

{
    tag,
    key,
    elementType,
    type,
    stateNode,
    return,
    child,
    sibling,
    index,
    ref,
    pendingProps,
    memoizedProps,
    updateQueue,
    memoizedState,
    firstContextDependency,
    mode,
    effectTag,
    nextEffect,
    firstEffect,
    lastEffect,
    expirationTime,
    childExpirationTime,
    alternate,
    actualDuration,
    actualStartTime,
    selfBaseDuration,
    treeBaseDuration
}
複製代碼

在render函數中建立的React Element樹在第一次渲染的時候會建立一顆結構如出一轍的Fiber節點樹。不一樣的React Element類型對應不一樣的Fiber節點類型。一個React Element的工做就由它對應的Fiber節點來負責。

Fiber的優先級以下:

高優先級會打斷正在執行的低優先級任務先執行。

一個React Element能夠對應不止一個Fiber,由於Fiber在更新的時候,會從原來的Fiber(current)克隆出一個新的Fiber(alternate)。兩個Fiber diff出的變化(side effect)記錄在alternate上。因此一個組件在更新時最多會有兩個Fiber與其對應,在更新結束後alternate會取代以前的current的成爲新的current節點。

這是fiber在目前版本v16.6.3所維護的全部屬性,具體想要了解閱讀源碼請看這裏。ReactFiber.js

setState

在官方文檔中,明確指出,要把state認做是不可變的,因此,如今更推崇的寫法不是直接setState,而是經過setState的回調函數進行更改。

this.setState(() => {[key]: value});
複製代碼

好,不說題外話了,讓咱們進入今天的正題,setState。 你們寫項目的時候,在index.js文件中,會引入兩個文件,react,react-dom。setState在react文件是這樣的:

熟不熟悉?Conmponent類,在這裏面咱們能夠看看幹了什麼事情,接受props,context和updater,注意我拿紅線標出來的部分,短路運算,再看看註釋,這個updater是隨後注入進去的。先不論是何時注入進去的,讓咱們接着往下看,setState確定會觸發更新,那咱們就沿着this.updater往下走,去尋找ReactNoopUpdateQueue(react空操做更新隊列),不少人會犯嘀咕,都空操做了還要更新什麼?耐心點,這裏的確是不進行任何更新操做,只是驗證一個數據格式,和檢驗舊版V8引擎的一些錯誤,並拋出來。
這是什麼?setState?幹了什麼?參數校驗,若是經過就執行下面的方法,this指的當前實例。
在enqueueSetState方法中,也是實例驗證。驗證明例是否mounted。

在你的應用第一次渲染的時候,最主要的是關注react-dom的進行,前面說過updater是隨後注入進去的,就是在react-dom加載的時候注入進去的。接下來,setState帶你們去看看到底是什麼?

直接來看setState隊列,這裏須要3個參數能夠看到分別是實例對象,載荷和回調函數。在這裏咱們先看在最開始生命4個變量分別是幹什麼用的,直接語義化就能猜出個大概來。

Q1:fiber經過get方法獲取一些東西?

A1: 能夠看到,源代碼實現的方法,獲再結合當前調用方法的上下文能夠得知,當前的fiber獲取到時當前實例上的一個_reactInternalFiber的值。這個值是什麼,實際上是經過相應的一個set方法,將當前實例和workInProgress傳入,並給賦值給當前實例的_reactInternalFiber屬性。

Q2:currentTime獲取當前的時間?

A2:

  • 首先判斷是否正在渲染中,是的話就返回最近一次的調度時間
  • 若是不在渲染中的話,會檢查是否有上次遺留的待處理的工做。
  • 若是nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never,來判斷優先級。
  • 從新計算當前的渲染時做爲調度時間,而且return;
  • 若是上次有遺留,則直接返回當前調度時間。
  • rederingTime 能夠隨時更新,currentSechedulerTime只有在沒有新任務的時候才更新

Q3:expirationTime獲取到期時間?什麼鬼?

A3:

  • 在此時,會進入第一個if條件判斷,經過判斷當前是否存在正在執行的上下文時間,是否正在進行渲染,仍是其餘狀況。
  • 若是存在expirationContext,則到期時間就是修改成當前的上下文執行時間。
  • 若是正在調度時間的話,判斷是否處於commit階段,是的話就設置爲同步優先級,不然的話就賦值爲下次渲染到期時間。
  • 若是上述狀況都不知足的狀況下,就會計算當前實例fiber的優先級。
  • 這裏分爲異步和同步,分別調用不一樣的方法進行計算,得到優先級後則和同步更新同樣, 建立update並放進隊列, 而後調用sheuduleWork
  • 在這裏還會有交互式刷新的判斷,是追蹤最短待處理的交互式到期時間。 這容許咱們在須要時同步刷新全部交互式更新。
  • 最後返回當前所須要的到期時間。
  • 此步驟和2步驟能夠合併爲計算優先級

Q4:update建立update隊列?

A4: 這個階段就是經過createUpdate來建立一個更新對象。

在進行了一系列不可描述的過程以後,終於能夠進行接下來的操做了。

首先調用flushPassiveEffects()來進行刷新,將被動影響的屬性刷新一遍,接着是重頭戲,調用enqueueUpdate()方法,將須要更新的fiber放入更新隊列。

這裏其實就是這麼個原理:

第一部分

  1. 首先判斷是否是隻有一個fiber,只有一個fiber的話就讓q1等於這個值,而後q2克隆q1
  2. 若是是有倆個fiber,則q1等於當前實例的fiber.updateQueue,q2就等於alternate.updateQueue;
  3. 若是兩個fiber都沒有更新隊列。則q1,q2都建立新的。
  4. 只有一個fiber有更新隊列。克隆以建立一個新的。
  5. 倆個fiber都有更新隊列。總之就是,q1和q2都須要有一個fiber。

第二部分

  1. 當q1與q2是相等時,一位置實際上只有一個fiber,將此fiber插入到更新隊列;
  2. 若q1和q2有一個是非空隊列,則兩個對列都須要更新;
  3. 當q1和q2兩個隊列都是非空,因爲結構共享,兩個列表中的最後一次更新是相同的。所以,只需q1添加到更新隊列便可;
  4. 最後將q2的lastUpdate指針更新。

最後一步,就是掉用scheduleWork()方法,來進行最後的更新。在此方法中會根據優先級進行分片式更新。

  1. 首先調用scheduleWorkToRoot()方法,更新fiber的優先級,遍歷到根組件的父級路徑,並更新子組件的優先級。
  2. 爲先前未計劃的交互更新掛起的異步工做計數。
  3. 更新當前交互的掛起的異步工做計數。
  4. 監聽更新列表的變化,返回root。

接下來,在commit階段,一口氣執行完畢。你的DOM就是最新的了。說了這麼多,可能執行起來,就是短短的幾十毫秒... 就好比下面

image

至此,setState整個過程算是完成了。

總結:這篇文章是鄙人第一次下手書寫,有些地方可能表述不是很準確,可能有點囉嗦,可是我喜歡啊。俗話說萬事開頭難,可是過程也難啊,結果更難啊。對於代碼也同樣,要堅持下去,堅持下去你就得頸椎病了哦。本文有什麼錯誤的地方,還煩請各路大神指出,鄙人是不會改滴,都會記在內心噠,上述是我對setState的理解,拋磚引玉,但願幫助你們有方向的去了解react原理機制。

相關文章
相關標籤/搜索