爲何是setState,由於對於你們而言,大多數使用react的新手或者初學者,大多會直接接觸到setState,並且這個方法也多是接觸最多的操做方法。那麼要想詳細瞭解setState究竟在React中作了什麼事情,就須要深刻了解一下。而在最新的React 16版本中,React的核心渲染框架時進行過一次升級的,由以前的React升級到了React Fiber。(PS:本文針對菜鳥、初級工程師而寫,有錯誤不足之處,請各位大佬指出更正。感受太low,請繞道,謝謝。)前端
彆着急,讓我來慢慢給大家解答,在16版本以前,React使用的仍是舊版的渲染核心,它的渲染過程是一口氣完成,怎麼理解呢?就是會一次性遍歷你全部的Dom節點,這個過程取決於你的應用的複雜程度。固然,這個過程通常比較快,可是也不排除在大型複雜應用中出現比較長的等待時間,這個時間是基於ms級別的。而做爲一個前端工程師,性能優化是比較重要的一方面之一,你們都知道,瀏覽器是的渲染引擎是單線程的,這就意味着一個時間段以內只能完成一件事。當你的應用過於複雜時,用戶操做變多,弊端就顯示出來了:卡頓,未響應,甚至是頁面崩潰...這就是爲何React會升級到React Fiber,在未升級以前,渲染模式是這樣的:react
假設你的結構是這樣的 A組件 => B組件 => C/D/E組件 D組件 => F組件 未使用Fiber架構的渲染方式git
他的舊版渲染模式是這樣的: github
在升級爲Fiber以後,就如同游泳同樣,每一個一段時間,都須要上岸呼吸一口氣,因此渲染模式就變成更瞭如下狀況: 算法
潛水員會每隔一段時間就上岸,看是否有更重要的事情要作。數組
加入fiber的react將組件更新分爲兩個階段,Reconcile階段和Commit階段。瀏覽器
經過這個倆個階段,你就會明白,爲何以前會把componentWillMount、componentWillReviceProps和componentWillUpdate標記爲不安全的生命週期函數了,由於在Reconcile階段,被打斷以後是從新進行的,就有可能形成對此的數據請求,對此渲染,形成沒必要要的資源、性能浪費(這裏有一個比較有意思飢餓問題,聰明的同窗應該已經猜出來了,react如今尚未公佈解決方法哦)。安全
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
在官方文檔中,明確指出,要把state認做是不可變的,因此,如今更推崇的寫法不是直接setState,而是經過setState的回調函數進行更改。
this.setState(() => {[key]: value});
複製代碼
好,不說題外話了,讓咱們進入今天的正題,setState。 你們寫項目的時候,在index.js文件中,會引入兩個文件,react,react-dom。setState在react文件是這樣的:
在你的應用第一次渲染的時候,最主要的是關注react-dom的進行,前面說過updater是隨後注入進去的,就是在react-dom加載的時候注入進去的。接下來,setState帶你們去看看到底是什麼?
直接來看setState隊列,這裏須要3個參數能夠看到分別是實例對象,載荷和回調函數。在這裏咱們先看在最開始生命4個變量分別是幹什麼用的,直接語義化就能猜出個大概來。
Q1:fiber經過get方法獲取一些東西?
A1: 能夠看到,源代碼實現的方法,獲再結合當前調用方法的上下文能夠得知,當前的fiber獲取到時當前實例上的一個_reactInternalFiber的值。這個值是什麼,實際上是經過相應的一個set方法,將當前實例和workInProgress傳入,並給賦值給當前實例的_reactInternalFiber屬性。
Q2:currentTime獲取當前的時間?
Q3:expirationTime獲取到期時間?什麼鬼?
Q4:update建立update隊列?
A4: 這個階段就是經過createUpdate來建立一個更新對象。
在進行了一系列不可描述的過程以後,終於能夠進行接下來的操做了。
第一部分
- 首先判斷是否是隻有一個fiber,只有一個fiber的話就讓q1等於這個值,而後q2克隆q1
- 若是是有倆個fiber,則q1等於當前實例的fiber.updateQueue,q2就等於alternate.updateQueue;
- 若是兩個fiber都沒有更新隊列。則q1,q2都建立新的。
- 只有一個fiber有更新隊列。克隆以建立一個新的。
- 倆個fiber都有更新隊列。總之就是,q1和q2都須要有一個fiber。
第二部分
- 當q1與q2是相等時,一位置實際上只有一個fiber,將此fiber插入到更新隊列;
- 若q1和q2有一個是非空隊列,則兩個對列都須要更新;
- 當q1和q2兩個隊列都是非空,因爲結構共享,兩個列表中的最後一次更新是相同的。所以,只需q1添加到更新隊列便可;
- 最後將q2的lastUpdate指針更新。
最後一步,就是掉用scheduleWork()方法,來進行最後的更新。在此方法中會根據優先級進行分片式更新。
接下來,在commit階段,一口氣執行完畢。你的DOM就是最新的了。說了這麼多,可能執行起來,就是短短的幾十毫秒... 就好比下面
至此,setState整個過程算是完成了。
總結:這篇文章是鄙人第一次下手書寫,有些地方可能表述不是很準確,可能有點囉嗦,可是我喜歡啊。俗話說萬事開頭難,可是過程也難啊,結果更難啊。對於代碼也同樣,要堅持下去,堅持下去你就得頸椎病了哦。本文有什麼錯誤的地方,還煩請各路大神指出,鄙人是不會改滴,都會記在內心噠,上述是我對setState的理解,拋磚引玉,但願幫助你們有方向的去了解react原理機制。