React Fiber架構:可控的「調用棧」

React 16採用新的Fiber架構對React進行徹底重寫,同時保持向後兼容。react

動機:concurrent rendering

concurrent rendering 又叫 async rendering,主要包含2個特性:git

  • time slicing(分片)github

    • 爲了讓瀏覽器保持60fps,所以渲染一幀須要在16.67ms內完成,不然會形成「卡頓」
    • time slicing將渲染工做切分,從而保證JavaScript的執行不會形成卡頓
    • 另外一個功能是,將渲染工做按重要性來排序,提升時間敏感(time-sensitive)渲染的優先級(好比text input)
  • suspensesegmentfault

    • 讓任何一個組件可以暫停渲染,等待數據的獲取(好比懶加載組件、好比網絡請求數據)

這兩個特性的關鍵前提是:React的渲染可以被停止(interrupt)、恢復。
這就是爲何咱們須要fiber架構了。promise

背景:JavaScript的執行模型:call stack

首先,咱們先解釋,爲何過去的架構沒法支持渲染停止。瀏覽器

JavaScript原生的執行模型:經過調用棧來管理函數執行狀態。網絡

其中每一個棧幀表示一個工做單元(a unit of work),存儲了函數調用的返回指針、當前函數、調用參數、局部變量等信息。
由於JavaScript的執行棧是由引擎管理的,執行棧一旦開始,就會一直執行,直到執行棧清空。沒法按需停止。架構

這與React有什麼關係呢?React將視圖看作函數調用的結果:dom

View = Component(Data)

Component會遞歸調用其餘的Component。頁面複雜的話,這個調用棧會很深,致使UI變卡。
在React Fiber以前,React的渲染就是使用原生執行棧來管理組件樹的遞歸渲染。這意味着,整顆組件樹的渲染必須一次性完成,工做沒法被分片。
所以,react須要另外一種可控的執行模型,讓react來管理工做的調度。async

React Fiber架構:可控的「調用棧」

React Fiber架構就是用JavaScript來實現的執行模型。能夠將它比做由react管理的「調用棧」,一個fiber與一個函數棧幀很是相似,它們都表示一個工做單元(a unit of work)。一個組件實例對應一個Fiber。

函數棧幀 fiber
返回指針 父組件
當前函數 當前組件
調用參數 props
局部變量 state

React Fiber的構造函數源碼

React Fiber與調用棧的區別:

  • React Fiber是鏈表結構,過去的遞歸調用變成了對fiber的鏈表遍歷。fiber不只有return指針,還有child、sibling指針,有這三個指針的鏈表就可以實現深度優先遍歷(其實scheduler還可以更加靈活地調度,使得react可以優先執行重要組件的渲染)。
  • fiber與調用棧的另外一個區別是,棧幀在函數返回之後就銷燬了,而fiber會在渲染結束之後繼續存在,保存組件實例的信息。
React Fiber是使用JavaScript實現的,這意味着它的底層依然是JavaScript調用棧。

Fiber實際上是計算機科學中早已存在的概念。Fiber的英文含義就是「纖維」,意指比Thread更細的線,寓意它是比線程(Thread)控制得更精密的執行模型。fiber是協做的(cooperatively)、可控的。一個fiber執行完本身的工做之後,會主動讓出控制權,不會主宰(dominate)整個程序的執行。

協程(Coroutines)基本是相同的概念,它們的區別微乎其微。說白了,React Fiber就是用JavaScript從新實現了一個協程模型。
話說回來,generator函數也可以主動讓出程序控制權(generator函數本質就是協程),用它也可以作到concurrent rendering。爲何react不使用generator函數而是從新實現協程,應該是由於後者可以更加靈活吧,好比generator函數不支持回到以前的yield狀態,而fiber支持從任意一個fiber節點從新開始渲染。

與Fiber相反,調用棧模型則不可控、不協做(non-cooperatively)。若是函數不斷地遞歸調用,那麼會徹底主宰整個程序,後續的工做(好比瀏覽器paint)必須等待它執行完成。

摘自React Fiber是什麼:

在React Fiber中,一次更新過程會分紅多個分片完成,因此徹底有可能一個更新任務尚未完成,就被另外一個更高優先級的更新過程打斷,這時候,優先級高的更新任務會優先處理完,而低優先級更新任務所作的工做則會 徹底做廢,而後等待機會重頭再來
由於一個更新過程可能被打斷,因此React Fiber一個更新過程被分爲兩個階段(Phase):第一個階段Reconciliation Phase和第二階段Commit Phase。
在第一階段Reconciliation Phase,React Fiber會找出須要更新哪些DOM,這個階段是能夠被打斷的;可是到了第二階段Commit Phase,那就一氣呵成把DOM更新完,毫不會被打斷。

生命週期示意圖

clipboard.png

Fiber架構如何知足前述的「動機」

time slicing(分片)

當一個Fiber的工做執行完,控制權會交還給React Scheduler,後者會檢查【渲染一幀的可用時間】是否已經用完:

  • 若是還有足夠的時間,那麼React Scheduler會將控制權交給下一個Fiber。
  • 若是時間不足,那麼React Scheduler會經過requestIdleCallback讓瀏覽器在空閒的時候喚醒本身,而後將控制權交還給瀏覽器(執行棧清空即瀏覽器得到控制權)。

Suspense

若是渲染到某個組件時,發現渲染須要暫停(好比須要等待React.lazy組件的加載,咱們假設組件層級爲<App> -> <User> -> <LazyComponent>),那麼在User組件的渲染函數中,會拋出一個Promise。得益於React Fiber架構,調用棧並非React scheduler -> App -> User,而是:先React scheduler -> App而後React scheduler -> User。所以User組件拋出的錯誤會被React scheduler接住,React scheduler會將渲染「暫停」在User組件。這意味着,App組件的工做不會丟失。等到promise解析到數據之後,從User fiber開始從新渲染就行了(至關於控制權直接交還給User)。

Algebraic Effects,以及它在React中的應用討論了它背後的理論概念。

參考資料

Algebraic effects, Fibers, Coroutines...
React Fiber Architecture
Inside Fiber: in-depth overview of the new reconciliation algorithm in React

<!--Continuations, coroutines, fibers, effects-->

相關文章
相關標籤/搜索