
背景
前段時間準備前端招聘事項,復習前端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)。
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占用的,因此任何交互、布局、渲染都會停止,給用戶的感覺就是頁面被卡住了。

網(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)化思路

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源碼分析 第三篇(異步狀態(tài))
優(yōu)先級

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í)行
生命周期

生命周期函數(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的渲染機制遵循同步渲染:
首次渲染: willMount > render > didMount,
props更新時: receiveProps > shouldUpdate > willUpdate > render > didUpdate
state更新時: shouldUpdate > willUpdate > render > didUpdate
卸載時: 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 是什么?