react中什麼是fiber

2022-03-22 16:00:38

Fiber是React新的排程演演算法,是對核心演演算法的一次重新實現。React Fiber把更新過程碎片化,每執行完一段更新過程,就把控制權交還給React負責任務協調的模組,看看有沒有其他緊急任務要做,如果有緊急任務,就去做緊急任務。

本教學操作環境:Windows7系統、react17.0.1版、Dell G3電腦。

react16 版本之後引入了 fiber,整個架構層面的 排程、協調、diff 演演算法以及渲染等都與 fiber 密切相關。

React Fiber是個什麼東西呢?

react在進行元件渲染時,從setState開始到渲染完成整個過程是同步的(「一氣呵成」)。如果需要渲染的元件比較龐大,js執行會佔據主執行緒時間較長,會導致頁面響應度變差,使得react在動畫、手勢等應用中效果比較差。

為了解決這個問題,react團隊經過兩年的工作,重寫了react中核心演演算法——reconciliation。並在v16版本中釋出了這個新的特性。為了區別之前和之後的reconciler,通常將之前的reconciler稱為stack reconciler,重寫後的稱為fiber reconciler,簡稱為Fiber。

官方的一句話解釋是「React Fiber是對核心演演算法的一次重新實現」。

Fiber 可以提升複雜React 應用的可響應性和效能。Fiber 即是React新的排程演演算法(reconciliation algorithm)

React Fiber 把更新過程碎片化,每執行完一段更新過程,就把控制權交還給 React 負責任務協調的模組,看看有沒有其他緊急任務要做,如果沒有就繼續去更新,如果有緊急任務,那就去做緊急任務。

react在進行元件渲染時,從setState開始到渲染完成整個過程是同步的(「一氣呵成」)。如果需要渲染的元件比較龐大,js執行會佔據主執行緒時間較長,會導致頁面響應度變差,使得react在動畫、手勢等應用中效果比較差。

為了解決這個問題,react團隊經過兩年的工作,重寫了react中核心演演算法——reconciliation。並在v16版本中釋出了這個新的特性。為了區別之前和之後的reconciler,通常將之前的reconciler稱為stack reconciler,重寫後的稱為fiber reconciler,簡稱為Fiber。

1.gif

2.gif

卡頓原因

Stack reconciler的工作流程很像函數的呼叫過程。父元件裡調子元件,可以類比為函數的遞迴(這也是為什麼被稱為stack reconciler的原因)。在setState後,react會立即開始reconciliation過程,從父節點(Virtual DOM)開始遍歷,以找出不同。將所有的Virtual DOM遍歷完成後,reconciler才能給出當前需要修改真實DOM的資訊,並傳遞給renderer,進行渲染,然後螢幕上才會顯示此次更新內容。對於特別龐大的vDOM樹來說,reconciliation過程會很長(x00ms),在這期間,主執行緒是被js佔用的,因此任何互動、佈局、渲染都會停止,給使用者的感覺就是頁面被卡住了。

3.gif

Scheduler

scheduling(排程)是fiber reconciliation的一個過程,主要決定應該在何時做什麼。?的過程表明在stack reconciler中,reconciliation是「一氣呵成」,對於函數來說,這沒什麼問題,因為我們只想要函數的執行結果,但對於UI來說還需要考慮以下問題:

  • 並不是所有的state更新都需要立即顯示出來,比如螢幕之外的部分的更新
  • 並不是所有的更新優先順序都是一樣的,比如使用者輸入的響應優先順序要比通過請求填充內容的響應優先順序更高
  • 理想情況下,對於某些高優先順序的操作,應該是可以打斷低優先順序的操作執行的,比如使用者輸入時,頁面的某個評論還在reconciliation,應該優先響應使用者輸入

所以理想狀況下reconciliation的過程應該是像下圖所示一樣,每次只做一個很小的任務,做完後能夠「喘口氣兒」,回到主執行緒看下有沒有什麼更高優先順序的任務需要處理,如果又則先處理更高優先順序的任務,沒有則繼續執行(cooperative scheduling 合作式排程)。

4.gif

任務拆分 fiber-tree & fiber

先看一下stack-reconciler下的react是怎麼工作的。程式碼中建立(或更新)一些元素,react會根據這些元素建立(或更新)Virtual DOM,然後react根據更新前後virtual DOM的區別,去修改真正的DOM。注意,在stack reconciler下,DOM的更新是同步的,也就是說,在virtual DOM的比對過程中,發現一個instance有更新,會立即執行DOM操作

5.gif

而fiber-conciler下,操作是可以分成很多小部分,並且可以被中斷的,所以同步操作DOM可能會導致fiber-tree與實際DOM的不同步。對於每個節點來說,其不光儲存了對應元素的基本資訊,還要儲存一些用於任務排程的資訊。因此,fiber僅僅是一個物件,表徵reconciliation階段所能拆分的最小工作單元,和上圖中的react instance一一對應。通過stateNode屬性管理Instance自身的特性。通過child和sibling表徵當前工作單元的下一個工作單元,return表示處理完成後返回結果所要合併的目標,通常指向父節點。整個結構是一個連結串列樹。每個工作單元(fiber)執行完成後,都會檢視是否還繼續擁有主執行緒時間片,如果有繼續下一個,如果沒有則先處理其他高優先順序事務,等主執行緒空閒下來繼續執行。

6.gif

fiber {  	stateNode: {},    child: {},    return: {},    sibling: {},
}複製程式碼

舉個例子

當前頁面包含一個列表,通過該列表渲染出一個button和一組Item,Item中包含一個div,其中的內容為數位。通過點選button,可以使列表中的所有數位進行平方。另外有一個按鈕,點選可以調節字型大小。

7.gif

頁面渲染完成後,就會初始化生成一個fiber-tree。初始化fiber-tree和初始化Virtual DOM tree沒什麼區別,這裡就不再贅述。

8.gif

於此同時,react還會維護一個workInProgressTree。workInProgressTree用於計算更新,完成reconciliation過程。

9.gif

使用者點選平方按鈕後,利用各個元素平方後的list呼叫setState,react會把當前的更新送入list元件對應的update queue中。但是react並不會立即執行對比並修改DOM的操作。而是交給scheduler去處理。

10.gif

scheduler會根據當前主執行緒的使用情況去處理這次update。為了實現這種特性,使用了requestIdelCallbackAPI。對於不支援這個API的瀏覽器,react會加上pollyfill。

總的來講,通常,使用者端執行緒執行任務時會以幀的形式劃分,大部分裝置控制在30-60幀是不會影響使用者體驗;在兩個執行幀之間,主執行緒通常會有一小段空閒時間,requestIdleCallback可以在這個空閒期(Idle Period)呼叫空閒期回撥(Idle Callback),執行一些任務

11.gif

  • 低優先順序任務由requestIdleCallback處理;

  • 高優先順序任務,如動畫相關的由requestAnimationFrame處理;

  • requestIdleCallback可以在多個空閒期呼叫空閒期回撥,執行任務;

  • requestIdleCallback方法提供deadline,即任務執行限制時間,以切分任務,避免長時間執行,阻塞UI渲染而導致掉幀;

一旦reconciliation過程得到時間片,就開始進入work loop。work loop機制可以讓react在計算狀態和等待狀態之間進行切換。為了達到這個目的,對於每個loop而言,需要追蹤兩個東西:下一個工作單元(下一個待處理的fiber);當前還能佔用主執行緒的時間。第一個loop,下一個待處理單元為根節點。

12.gif

因為根節點上的更新佇列為空,所以直接從fiber-tree上將根節點複製到workInProgressTree中去。根節點中包含指向子節點(List)的指標。

13.gif

根節點沒有什麼更新操作,根據其child指標,接下來把List節點及其對應的update queue也複製到workinprogress中。List插入後,向其父節點返回,標誌根節點的處理完成。

14.gif

根節點處理完成後,react此時檢查時間片是否用完。如果沒有用完,根據其儲存的下個工作單元的資訊開始處理下一個節點List。

15.gif

接下來進入處理List的work loop,List中包含更新,因此此時react會呼叫setState時傳入的updater funciton獲取最新的state值,此時應該是[1,4,9]。通常我們現在在呼叫setState傳入的是一個物件,但在使用fiber conciler時,必須傳入一個函數,函數的返回值是要更新的state。react從很早的版本就開始支援這種寫法了,不過通常沒有人用。在之後的react版本中,可能會廢棄直接傳入物件的寫法。

setState({}, callback); // stack concilersetState(() => { return {} }, callback); // fiber conciler複製程式碼

在獲取到最新的state值後,react會更新List的state和props值,然後呼叫render,然後得到一組通過更新後的list值生成的elements。react會根據生成elements的型別,來決定fiber是否可重用。對於當前情況來說,新生成的elments型別並沒有變(依然是Button和Item),所以react會直接從fiber-tree中複製這些elements對應的fiber到workInProgress 中。並給List打上標籤,因為這是一個需要更新的節點。

16.gif

List節點處理完成,react仍然會檢查當前時間片是否夠用。如果夠用則處理下一個,也就是button。加入這個時候,使用者點選了放大字型的按鈕。這個放大字型的操作,純粹由js實現,跟react無關。但是操作並不能立即生效,因為react的時間片還未用完,因此接下來仍然要繼續處理button。

17.gif

button沒有任何子節點,所以此時可以返回,並標誌button處理完成。如果button有改變,需要打上tag,但是當前情況沒有,只需要標記完成即可。

18.gif

老規矩,處理完一個節點先看時間夠不夠用。注意這裡放大字型的操作已經在等候釋放主執行緒了。

19.gif

接下來處理第一個item。通過shouldComponentUpdate勾點可以根據傳入的props判斷其是否需要改變。對於第一個Item而言,更改前後都是1,所以不會改變,shouldComponentUpdate返回false,複製div,處理完成,檢查時間,如果還有時間進入第二個Item。

第二個Item shouldComponentUpdate返回true,所以需要打上tag,標誌需要更新,複製div,呼叫render,講div中的內容從2更新為4,因為div有更新,所以標記div。當前節點處理完成。

20.gif

對於上面這種情況,div已經是葉子節點,且沒有任何兄弟節點,且其值已經更新,這時候,需要將此節點改變產生的effect合併到父節點中。此時react會維護一個列表,其中記錄所有產生effect的元素。

21.gif

合併後,回到父節點Item,父節點標記完成。

22.gif

下一個工作單元是Item,在進入Item之前,檢查時間。但這個時候時間用完了。此時react必須交換主執行緒,並告訴主執行緒以後要為其分配時間以完成剩下的操作。

23.gif

主執行緒接下來進行放大字型的操作。完成後執行react接下來的操作,跟上一個Item的處理流程幾乎一樣,處理完成後整個fiber-tree和workInProgress如下:

24.gif

完成後,Item向List返回並merge effect,effect List現在如下所示:

25.gif

此時List向根節點返回並merge effect,所有節點都可以標記完成了。此時react將workInProgress標記為pendingCommit。意思是可以進入commit階段了。

26.gif

此時,要做的是還是檢查時間夠不夠用,如果沒有時間,會等到時間再去提交修改到DOM。進入到階段2後,reacDOM會根據階段1計算出來的effect-list來更新DOM。

更新完DOM之後,workInProgress就完全和DOM保持一致了,為了讓當前的fiber-tree和DOM保持一直,react交換了current和workinProgress兩個指標。

27.gif

事實上,react大部分時間都在維持兩個樹(Double-buffering)。這可以縮減下次更新時,分配記憶體、垃圾清理的時間。commit完成後,執行componentDidMount函數。

小結

通過將reconciliation過程,分解成小的工作單元的方式,可以讓頁面對於瀏覽器事件的響應更加及時。但是另外一個問題還是沒有解決,就是如果當前在處理的react渲染耗時較長,仍然會阻塞後面的react渲染。這就是為什麼fiber reconciler增加了優先順序策略。

【相關推薦:Redis視訊教學

以上就是react中什麼是fiber的詳細內容,更多請關注TW511.COM其它相關文章!