淺談React Fiber

圖片描述

背景

前段時間準備前端招聘事項,複習前端React相關知識;複習React16新的生命週期:棄用了componentWillMountcomponentWillReceivePorpscomponentWillUpdate三個生命週期, 新增了getDerivedStateFromPropsgetSnapshotBeforeUpdate來代替棄用的三個鉤子函數。
發現React生命週期的文章不多說到 React 官方爲何要棄用這三生命週期的緣由, 查閱相關資料瞭解到根本緣由是V16版本重構核心算法架構:React Fiber;查閱資料過程當中對React Fiber有了必定了解,本文就相關資料整理出我的對Fiber的理解, 與你們一塊兒簡單認識下 React Fiberhtml

React Fiber是什麼?

官方的一句話解釋是「React Fiber是對核心算法的一次從新實現」。Fiber 架構調整很早就官宣了,但官方通過兩年時間纔在V16版本正式發佈。官方概念解釋太籠統, 其實簡單來講 React Fiber 是一個新的任務調和器(Reconciliation), 本文後續將詳細解釋。前端

爲何叫 「Fiber」?

你們應該都清楚進程(Process)和線程(Thread)的概念,進程是操做系統分配資源的最小單元,線程是操做系統調度的最小單元,在計算機科學中還有一個概念叫作Fiber,英文含義就是「纖維」,意指比Thread更細的線,也就是比線程(Thread)控制得更精密的併發處理機制。
上面說的Fiber和React Fiber不是相同的概念,可是,React團隊把這個功能命名爲Fiber,含義也是更加緊密的處理機制,比Thread更細。react

Fiber 架構解決了什麼問題?

爲何官方要花2年多的時間來重構React 核心算法?
首先要從Fiber算法架構前 React 存在的問題提及!提及React算法架構避不開「Reconciliaton」。算法

Reconciliation

React 官方核心算法名稱是 Reconciliation , 中文翻譯是「協調」!React diff 算法的實現 就與之相關。
先簡單回顧下React Diff: React獨創了「虛擬DOM」概念, 「虛擬DOM」能火併流行起來主要緣由在於該概念對前端性能優化的突破性創新;
稍微瞭解瀏覽器加載頁面原理的前端同窗都知道網頁性能問題大都出如今DOM節點頻繁操做上;
而React經過「虛擬DOM」 + React Diff算法保證了前端性能;編程

傳統Diff算法

經過循環遞歸對節點進行依次對比,算法複雜度達到 O(n^3) ,n是樹的節點數,這個有多可怕呢?——若是要展現1000個節點,得執行上億次比較。。即使是CPU快能執行30億條命令,也很難在一秒內計算出差別。segmentfault

React Diff算法

將Virtual DOM樹轉換成actual DOM樹的最少操做的過程 稱爲 協調(Reconciliaton)。
React Diff三大策略
1.tree diff;
2.component diff;
3.element diff;
PS: 以前H5開發遇到的State 中變量更新但視圖未更新的Bug就是element diff檢測致使。解決方案:1.兩種業務場景下的DOM節點儘可能避免雷同; 2.兩種業務場景下的DOM節點樣式避免雷同;react-native

在V16版本以前 協調機制Stack reconciler, V16版本發佈Fiber 架構後是 Fiber 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的工做流程很像函數的調用過程。父組件裏調子組件,能夠類比爲函數的遞歸(這也是爲何被稱爲stack reconciler的緣由)。
在setState後,react會當即開始reconciliation過程,從父節點(Virtual DOM)開始遍歷,以找出不一樣。將全部的Virtual DOM遍歷完成後,reconciler才能給出當前須要修改真實DOM的信息,並傳遞給renderer,進行渲染,而後屏幕上纔會顯示這次更新內容。
對於特別龐大的DOM樹來講,reconciliation過程會很長(x00ms),在這期間,主線程是被js佔用的,所以任何交互、佈局、渲染都會中止,給用戶的感受就是頁面被卡住了。緩存

clipboard.png

網友測試使用React V15,當DOM節點數量達到100000時, 加載頁面時間居然要7秒;詳情
固然以上極端狀況通常不會出現,官方爲了解決這種特殊狀況。在Fiber 架構中使用了Fiber reconciler。性能優化

Fiber reconciler

原來的React更新任務是採用遞歸形式,那麼如今若是任務想中斷, 在遞歸中是很難處理, 因此React改爲了大循環模式,修改了生命週期也是由於任務可中斷

Fiber reconciler 源碼

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

├── packages --------------------- React實現的相關代碼
│   ├── create-subscription ------ 在組件裏訂閱額外數據的工具
│   ├── events ------------------- React事件相關
│   ├── react -------------------- 組件與虛擬DOM模型
│   ├── react-art ---------------- 畫圖相關庫
│   ├── react-dom ---------------- ReactDom
│   ├── react-native-renderer ---- ReactNative
│   ├── react-reconciler --------- React調製器
│   ├── react-scheduler ---------- 規劃React初始化,更新等等
│   ├── react-test-renderer ------ 實驗性的React渲染器
│   ├── shared ------------------- 公共代碼
│   ├── simple-cache-provider ---- 爲React應用提供緩存

這裏面咱們主要關注 reconciler 這個模塊, packages/react-reconciler/src

├── react-reconciler ------------------------ reconciler相關代碼
│   ├── ReactFiberReconciler.js ------------- 模塊入口
├─ Model ----------------------------------------
│   ├── ReactFiber.js ----------------------- Fiber相關
│   ├── ReactUpdateQueue.js ----------------- state操做隊列
│   ├── ReactFiberRoot.js ------------------- RootFiber相關
├─ Flow -----------------------------------------
│   ├── ReactFiberScheduler.js -------------- 1.整體調度系統
│   ├── ReactFiberBeginWork.js -------------- 2.Fiber解析調度
│   ├── ReactFiberCompleteWork.js ----------- 3.建立DOM 
│   ├── ReactFiberCommitWork.js ------------- 4.DOM佈局
├─ Assist ---------------------------------------
│   ├── ReactChildFiber.js ------------------ children轉換成subFiber
│   ├── ReactFiberTreeReflection.js --------- 檢索Fiber
│   ├── ReactFiberClassComponent.js --------- 組件生命週期
│   ├── stateReactFiberExpirationTime.js ---- 調度器優先級
│   ├── ReactTypeOfMode.js ------------------ Fiber mode type
│   ├── ReactFiberHostConfig.js ------------- 調度器調用渲染器入口

Fiber reconciler 優化思路

clipboard.png

Fiber reconciler 使用了scheduling(調度)這一過程, 每次只作一個很小的任務,作完後可以「喘口氣兒」,回到主線程看下有沒有什麼更高優先級的任務須要處理,若是有則先處理更高優先級的任務,沒有則繼續執行(cooperative scheduling 合做式調度)。

網友測試使用React V16,當DOM節點數量達到100000時, 頁面能正常加載,輸入交互也正常了;詳情

因此Fiber 架構就是用 異步的方式解決舊版本 同步遞歸致使的性能問題。

Fiber 核心算法

編程最重要的是思想而不是代碼,本段主要理清Fiber架構內核算法的編碼思路;

Fiber 源碼解析

以前一個師弟問我關於Fiber的小問題:
Fiber 框架是否會自動給 Fiber Node打上優先級?
若是給Fiber Node打上的是async, 是否會給給它設置 expirationTime
帶着以上問題看源碼, 結論:
框架給每一個 Fiber Node 打上優先級(nowork, sync, async), 不論是sync 仍是 async都會給 該Fiber Node 設置expirationTime, expirationTime 越小優先級越高。

我的閱讀源碼細節就不放了, 由於發現網上有更系統的Fiber 源碼文章,雖然官方源碼已更新至Flow語法, 但算法並沒太大改變:
React Fiber源碼分析 (介紹)
React Fiber源碼分析 第一篇
React Fiber源碼分析 第二篇(同步模式)
React Fiber源碼分析 第三篇(異步狀態)

優先級

clipboard.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種優先級:
synchronous 與以前的Stack reconciler操做同樣,同步執行
task 在next tick以前執行
animation 下一幀以前執行
high 在不久的未來當即執行
low 稍微延遲(100-200ms)執行也不要緊
offscreen 下一次render時或scroll時才執行

生命週期

clipboard.png

生命週期函數也被分爲2個階段了:

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

// 第2階段 commit
componentDidMount
componentDidUpdate
componentWillUnmount

第1階段的生命週期函數可能會被屢次調用,默認以low優先級 執行,被高優先級任務打斷的話,稍後從新執行。

Fiber 架構對React開發影響

本段主要探討React V16 後Fiber架構對咱們使用React業務編程的影響有哪些?實際編碼須要注意哪些內容。

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
3) 卸載時: willUnmount
期間每一個周期函數各司其職,輸入輸出都是可預測,一路下來很順暢。
BUT 從React 17 開始,渲染機制將會發生顛覆性改變,這個新方式就是 Async Render。
首先,async render不是那種服務端渲染,好比發異步請求到後臺返回newState甚至新的html,這裏的async render仍是限制在React做爲一個View框架的View層自己。
經過進一步觀察能夠發現,預廢棄的三個生命週期函數都發生在虛擬dom的構建期間,也就是render以前。在未來的React 17中,在dom真正render以前,React中的調度機制可能會不按期的去查看有沒有更高優先級的任務,若是有,就打斷當前的週期執行函數(哪怕已經執行了一半),等高優先級任務完成,再回來從新執行以前被打斷的周期函數。這種新機制對現存周期函數的影響就是它們的調用時機變的複雜而不可預測,這也就是爲何」UNSAFE」。
做者:辰辰沉沉大辰沉
來源:CSDN

2.注意Fiber 優先級致使的bug;

瞭解Fiber原理後, 業務開發注意高優先級任務頻率,避免出現低優先級任務延遲過久執行或永不執行bug(starvation:低優先級餓死)。

3.業務邏輯實現別太依賴生命週期鉤子函數;

在Fiber架構中,task 有可能被打斷,須要從新執行,某些依賴生命週期實現的業務邏輯可能會受到影響。

參考文檔
React 新生命週期;
深刻理解進程和線程;
徹底理解React Fiber;
React Fiber;
如何閱讀大型項目源碼;
React 源碼解析;
React Fiber 源碼解析;
React Fiber 是什麼?
React diff 算法策略和實現
React 新引擎, React Fiber 是什麼?

相關文章
相關標籤/搜索