淺談React Fiber

react_fiber.jpeg

背景

前段時間準備前端招聘事項,復習前端React相關(guān)知識;復習React16新的生命周期:棄用了componentWillMount、componentWillReceivePorps,componentWillUpdate三個生命周期, 新增了getDerivedStateFromProps、getSnapshotBeforeUpdate來代替棄用的三個鉤子函數(shù)。

發(fā)現(xiàn)React生命周期的文章很少說到 React 官方為什么要棄用這三生命周期的原因, 查閱相關(guān)資料了解到根本原因是V16版本重構(gòu)核心算法架構(gòu):React Fiber;查閱資料過程中對React Fiber有了一定了解,本文就相關(guān)資料整理出個人對Fiber的理解, 與大家一起簡單認識下 React Fiber;

React Fiber是什么?

官方的一句話解釋是“React Fiber是對核心算法的一次重新實現(xiàn)”。Fiber 架構(gòu)調(diào)整很早就官宣了,但官方經(jīng)過兩年時間才在V16版本正式發(fā)布。官方概念解釋太籠統(tǒng), 其實簡單來說 React Fiber 是一個新的任務調(diào)和器(Reconciliation), 本文后續(xù)將詳細解釋。

為什么叫 “Fiber”?

大家應該都清楚進程(Process)和線程(Thread)的概念,進程是操作系統(tǒng)分配資源的最小單元,線程是操作系統(tǒng)調(diào)度的最小單元,在計算機科學中還有一個概念叫做Fiber,英文含義就是“纖維”,意指比Thread更細的線,也就是比線程(Thread)控制得更精密的并發(fā)處理機制。

上面說的Fiber和React Fiber不是相同的概念,但是,React團隊把這個功能命名為Fiber,含義也是更加緊密的處理機制,比Thread更細。

Fiber 架構(gòu)解決了什么問題?

為什么官方要花2年多的時間來重構(gòu)React 核心算法?

首先要從Fiber算法架構(gòu)前 React 存在的問題說起!說起React算法架構(gòu)避不開“Reconciliaton”。

Reconciliation

React 官方核心算法名稱是 Reconciliation , 中文翻譯是“協(xié)調(diào)”!React diff 算法的實現(xiàn) 就與之相關(guān)。

先簡單回顧下React Diff: React首創(chuàng)了“虛擬DOM”概念, “虛擬DOM”能火并流行起來主要原因在于該概念對前端性能優(yōu)化的突破性創(chuàng)新;

稍微了解瀏覽器加載頁面原理的前端同學都知道網(wǎng)頁性能問題大都出現(xiàn)在DOM節(jié)點頻繁操作上;

而React通過“虛擬DOM” + React Diff算法保證了前端性能;

傳統(tǒng)Diff算法

通過循環(huán)遞歸對節(jié)點進行依次對比,算法復雜度達到 O(n^3) ,n是樹的節(jié)點數(shù),這個有多可怕呢?——如果要展示1000個節(jié)點,得執(zhí)行上億次比較。。即便是CPU快能執(zhí)行30億條命令,也很難在一秒內(nèi)計算出差異。

React Diff算法

將Virtual DOM樹轉(zhuǎn)換成actual DOM樹的最少操作的過程稱為 協(xié)調(diào)(Reconciliaton)。

React Diff三大策略

1.tree diff;

2.component diff;

3.element diff;

PS: 之前H5開發(fā)遇到的State 中變量更新但視圖未更新的Bug就是element diff檢測導致。解決方案:1.兩種業(yè)務場景下的DOM節(jié)點盡量避免雷同; 2.兩種業(yè)務場景下的DOM節(jié)點樣式避免雷同;

在V16版本之前 協(xié)調(diào)機制 是 Stack reconciler, V16版本發(fā)布Fiber 架構(gòu)后是 F****iber reconciler。

Stack reconciler

Stack reconciler 源碼

// React V15: react/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
/**
 * ------------------ The Life-Cycle of a Composite Component ------------------
 *
 * - constructor: Initialization of state. The instance is now retained.
 *   - componentWillMount
 *   - render
 *   - [children's constructors]          // 子組件constructor()
 *     - [children's componentWillMount and render]   // 子組件willmount render
 *     - [children's componentDidMount]  // 子組件先于父組件完成掛載didmount
 *     - componentDidMount
 *
 *       Update Phases:
 *       - componentWillReceiveProps (only called if parent updated)
 *       - shouldComponentUpdate
 *         - componentWillUpdate
 *           - render
 *           - [children's constructors or receive props phases]
 *         - componentDidUpdate
 *
 *     - componentWillUnmount
 *     - [children's componentWillUnmount]
 *   - [children destroyed]
 * - (destroyed): The instance is now blank, released by React and ready for GC.
 *
 * -----------------------------------------------------------------------------

Stack reconciler 存在的問題

Stack reconciler的工作流程很像函數(shù)的調(diào)用過程。父組件里調(diào)子組件,可以類比為函數(shù)的遞歸(這也是為什么被稱為stack reconciler的原因)。

在setState后,react會立即開始reconciliation過程,從父節(jié)點(Virtual DOM)開始遍歷,以找出不同。將所有的Virtual DOM遍歷完成后,reconciler才能給出當前需要修改真實DOM的信息,并傳遞給renderer,進行渲染,然后屏幕上才會顯示此次更新內(nèi)容。

對于特別龐大的DOM樹來說,reconciliation過程會很長(x00ms),在這期間,主線程是被js占用的,因此任何交互、布局、渲染都會停止,給用戶的感覺就是頁面被卡住了。

image.png

網(wǎng)友測試使用React V15,當DOM節(jié)點數(shù)量達到100000時, 加載頁面時間竟然要7秒;詳情

當然以上極端情況一般不會出現(xiàn),官方為了解決這種特殊情況。在Fiber 架構(gòu)中使用了Fiber reconciler。

Fiber reconciler

原來的React更新任務是采用遞歸形式,那么現(xiàn)在如果任務想中斷, 在遞歸中是很難處理, 所以React改成了大循環(huán)模式,修改了生命周期也是因為任務可中斷。

Fiber reconciler 源碼

React的相關(guān)代碼都放在packages文件夾里。(PS: 源碼一直在更新,以下路徑有時效性不一定準確)

├── packages --------------------- React實現(xiàn)的相關(guān)代碼
│   ├── create-subscription ------ 在組件里訂閱額外數(shù)據(jù)的工具
│   ├── events ------------------- React事件相關(guān)
│   ├── react -------------------- 組件與虛擬DOM模型
│   ├── react-art ---------------- 畫圖相關(guān)庫
│   ├── react-dom ---------------- ReactDom
│   ├── react-native-renderer ---- ReactNative
│   ├── react-reconciler --------- React調(diào)制器
│   ├── react-scheduler ---------- 規(guī)劃React初始化,更新等等
│   ├── react-test-renderer ------ 實驗性的React渲染器
│   ├── shared ------------------- 公共代碼
│   ├── simple-cache-provider ---- 為React應用提供緩存

這里面我們主要關(guān)注 reconciler 這個模塊, packages/react-reconciler/src

├── react-reconciler ------------------------ reconciler相關(guān)代碼
│   ├── ReactFiberReconciler.js ------------- 模塊入口
├─ Model ----------------------------------------
│   ├── ReactFiber.js ----------------------- Fiber相關(guān)
│   ├── ReactUpdateQueue.js ----------------- state操作隊列
│   ├── ReactFiberRoot.js ------------------- RootFiber相關(guān)
├─ Flow -----------------------------------------
│   ├── ReactFiberScheduler.js -------------- 1.總體調(diào)度系統(tǒng)
│   ├── ReactFiberBeginWork.js -------------- 2.Fiber解析調(diào)度
│   ├── ReactFiberCompleteWork.js ----------- 3.創(chuàng)建DOM 
│   ├── ReactFiberCommitWork.js ------------- 4.DOM布局
├─ Assist ---------------------------------------
│   ├── ReactChildFiber.js ------------------ children轉(zhuǎn)換成subFiber
│   ├── ReactFiberTreeReflection.js --------- 檢索Fiber
│   ├── ReactFiberClassComponent.js --------- 組件生命周期
│   ├── stateReactFiberExpirationTime.js ---- 調(diào)度器優(yōu)先級
│   ├── ReactTypeOfMode.js ------------------ Fiber mode type
│   ├── ReactFiberHostConfig.js ------------- 調(diào)度器調(diào)用渲染器入口

Fiber reconciler 優(yōu)化思路

image.png

Fiber reconciler 使用了scheduling(調(diào)度)這一過程, 每次只做一個很小的任務,做完后能夠“喘口氣兒”,回到主線程看下有沒有什么更高優(yōu)先級的任務需要處理,如果有則先處理更高優(yōu)先級的任務,沒有則繼續(xù)執(zhí)行(cooperative scheduling 合作式調(diào)度)。

網(wǎng)友測試使用React V16,當DOM節(jié)點數(shù)量達到100000時, 頁面能正常加載,輸入交互也正常了;詳情

所以Fiber 架構(gòu)就是用 異步的方式解決舊版本 同步遞歸導致的性能問題。

Fiber 核心算法

編程最重要的是思想而不是代碼,本段主要理清Fiber架構(gòu)內(nèi)核算法的編碼思路;

Fiber 源碼解析

之前一個師弟問我關(guān)于Fiber的小問題:

Fiber 框架是否會自動給 Fiber Node打上優(yōu)先級?

如果給Fiber Node打上的是async, 是否會給給它設(shè)置 expirationTime?

帶著以上問題看源碼, 結(jié)論:

框架給每個 Fiber Node 打上優(yōu)先級(nowork, sync, async), 不管是sync 還是 async都會給 該Fiber Node 設(shè)置expirationTime, expirationTime 越小優(yōu)先級越高。

個人閱讀源碼細節(jié)就不放了, 因為發(fā)現(xiàn)網(wǎng)上有更系統(tǒng)的Fiber 源碼文章,雖然官方源碼已更新至Flow語法, 但算法并沒太大改變:

React Fiber源碼分析 (介紹)

React Fiber源碼分析 第一篇

React Fiber源碼分析 第二篇(同步模式)

React Fiber源碼分析 第三篇(異步狀態(tài))

優(yōu)先級

image.png
module.exports = {
  NoWork: 0, // No work is pending.
  SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
  AnimationPriority: 2, // Needs to complete before the next frame.
  HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
  LowPriority: 4, // Data fetching, or result from updating stores.
  OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};

React Fiber 每個工作單元運行時有6種優(yōu)先級:

  • synchronous 與之前的Stack reconciler操作一樣,同步執(zhí)行

  • task 在next tick之前執(zhí)行

  • animation 下一幀之前執(zhí)行

  • high 在不久的將來立即執(zhí)行

  • low 稍微延遲(100-200ms)執(zhí)行也沒關(guān)系

  • offscreen 下一次render時或scroll時才執(zhí)行

生命周期

image.png

生命周期函數(shù)也被分為2個階段了:

// 第1階段 render/reconciliation
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate

// 第2階段 commit
componentDidMount
componentDidUpdate
componentWillUnmount

第1階段的生命周期函數(shù)可能會被多次調(diào)用,默認以low優(yōu)先級 執(zhí)行,被高優(yōu)先級任務打斷的話,稍后重新執(zhí)行。

Fiber 架構(gòu)對React開發(fā)影響

本段主要探討React V16 后Fiber架構(gòu)對我們使用React業(yè)務編程的影響有哪些?實際編碼需要注意哪些內(nèi)容。

1.不使用官方宣布棄用的生命周期。

為了兼容舊代碼,官方并沒有立即在V16版本廢棄三生命周期, 用新的名字(帶上UNSAFE)還是能使用。 建議使用了V16+版本的React后就不要再使用廢棄的三生命周期。

因為React 17版本將真正廢棄這三生命周期:

到目前為止(React 16.4),React的渲染機制遵循同步渲染:

  1. 首次渲染: willMount > render > didMount,

  2. props更新時: receiveProps > shouldUpdate > willUpdate > render > didUpdate

  3. state更新時: shouldUpdate > willUpdate > render > didUpdate

  4. 卸載時: willUnmount
    期間每個周期函數(shù)各司其職,輸入輸出都是可預測,一路下來很順暢。
    BUT 從React 17 開始,渲染機制將會發(fā)生顛覆性改變,這個新方式就是 Async Render。
    首先,async render不是那種服務端渲染,比如發(fā)異步請求到后臺返回newState甚至新的html,這里的async render還是限制在React作為一個View框架的View層本身。
    通過進一步觀察可以發(fā)現(xiàn),預廢棄的三個生命周期函數(shù)都發(fā)生在虛擬dom的構(gòu)建期間,也就是render之前。在將來的React 17中,在dom真正render之前,React中的調(diào)度機制可能會不定期的去查看有沒有更高優(yōu)先級的任務,如果有,就打斷當前的周期執(zhí)行函數(shù)(哪怕已經(jīng)執(zhí)行了一半),等高優(yōu)先級任務完成,再回來重新執(zhí)行之前被打斷的周期函數(shù)。這種新機制對現(xiàn)存周期函數(shù)的影響就是它們的調(diào)用時機變的復雜而不可預測,這也就是為什么”UNSAFE”。

作者:辰辰沉沉大辰沉
來源:CSDN

2.注意Fiber 優(yōu)先級導致的bug;

了解Fiber原理后, 業(yè)務開發(fā)注意高優(yōu)先級任務頻率,避免出現(xiàn)低優(yōu)先級任務延遲太久執(zhí)行或永不執(zhí)行bug(starvation:低優(yōu)先級餓死)。

3.業(yè)務邏輯實現(xiàn)別太依賴生命周期鉤子函數(shù);

在Fiber架構(gòu)中,task 有可能被打斷,需要重新執(zhí)行,某些依賴生命周期實現(xiàn)的業(yè)務邏輯可能會受到影響。

參考文檔

React 新生命周期;
深入理解進程和線程;
完全理解React Fiber;
React Fiber;
如何閱讀大型項目源碼;
React 源碼解析;
React Fiber 源碼解析;
React Fiber 是什么?
React diff 算法策略和實現(xiàn)
React 新引擎, React Fiber 是什么?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容