React超詳細(xì)講述Fiber的使用
Fiber
概念
JavaScript引擎和頁面渲染引擎兩個(gè)線程是互斥的,當(dāng)其中一個(gè)線程執(zhí)行時(shí),另一個(gè)線程只能掛起等待
如果 JavaScript 線程長時(shí)間地占用了主線程,那么渲染層面的更新就不得不長時(shí)間地等待,界面長時(shí)間不更新,會(huì)導(dǎo)致頁面響應(yīng)度變差,用戶可能會(huì)感覺到卡頓
破解JavaScript中同步操作時(shí)間過長的方法其實(shí)很簡單——分片。
把一個(gè)耗時(shí)長的任務(wù)分成很多小片,每一個(gè)小片的運(yùn)行時(shí)間很短,雖然總時(shí)間依然很長,但是在每個(gè)小片執(zhí)行完之后,都給其他任務(wù)一個(gè)執(zhí)行的機(jī)會(huì),這樣唯一的線程就不會(huì)被獨(dú)占,其他任務(wù)依然有運(yùn)行的機(jī)會(huì)。
React Fiber把更新過程碎片化,每執(zhí)行完一段更新過程,就把控制權(quán)交還給React負(fù)責(zé)任務(wù)協(xié)調(diào)的模塊,看看有沒有其他緊急任務(wù)要做,如果沒有就繼續(xù)去更新,如果有緊急任務(wù),那就去做緊急任務(wù)。
維護(hù)每一個(gè)分片的數(shù)據(jù)結(jié)構(gòu),就是Fiber。
一個(gè) Fiber 代表一個(gè)工作單元。
在react中,主要做了以下的操作:
- 為每個(gè)增加了優(yōu)先級(jí),優(yōu)先級(jí)高的任務(wù)可以中斷低優(yōu)先級(jí)的任務(wù)。然后再重新,注意是重新執(zhí)行優(yōu)先級(jí)低的任務(wù)
- 增加了異步任務(wù),調(diào)用requestIdleCallback api,瀏覽器空閑的時(shí)候執(zhí)行
- dom diff樹變成了鏈表,一個(gè)dom對(duì)應(yīng)兩個(gè)fiber(一個(gè)鏈表),對(duì)應(yīng)兩個(gè)隊(duì)列,這都是為找到被中斷的任務(wù),重新執(zhí)行
從架構(gòu)角度來看,F(xiàn)iber 是對(duì) React核心算法(即調(diào)和過程)的重寫
從編碼角度來看,F(xiàn)iber是 React內(nèi)部所定義的一種數(shù)據(jù)結(jié)構(gòu),它是 Fiber樹結(jié)構(gòu)的節(jié)點(diǎn)單位,也就是 React 16 新架構(gòu)下的虛擬DOM
結(jié)構(gòu)
type Fiber = { // 用于標(biāo)記fiber的WorkTag類型,主要表示當(dāng)前fiber代表的組件類型如FunctionComponent、ClassComponent等 tag: WorkTag, // ReactElement里面的key key: null | string, // ReactElement.type,調(diào)用`createElement`的第一個(gè)參數(shù) elementType: any, // The resolved function/class/ associated with this fiber. // 表示當(dāng)前代表的節(jié)點(diǎn)類型 type: any, // 表示當(dāng)前FiberNode對(duì)應(yīng)的element組件實(shí)例 stateNode: any, // 指向他在Fiber節(jié)點(diǎn)樹中的`parent`,用來在處理完這個(gè)節(jié)點(diǎn)之后向上返回 return: Fiber | null, // 指向自己的第一個(gè)子節(jié)點(diǎn) child: Fiber | null, // 指向自己的兄弟結(jié)構(gòu),兄弟節(jié)點(diǎn)的return指向同一個(gè)父節(jié)點(diǎn) sibling: Fiber | null, index: number, ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject, // 當(dāng)前處理過程中的組件props對(duì)象 pendingProps: any, // 上一次渲染完成之后的props memoizedProps: any, // 該Fiber對(duì)應(yīng)的組件產(chǎn)生的Update會(huì)存放在這個(gè)隊(duì)列里面 updateQueue: UpdateQueue<any> | null, // 上一次渲染的時(shí)候的state memoizedState: any, // 一個(gè)列表,存放這個(gè)Fiber依賴的context firstContextDependency: ContextDependency<mixed> | null, mode: TypeOfMode, // Effect // 用來記錄Side Effect effectTag: SideEffectTag, // 單鏈表用來快速查找下一個(gè)side effect nextEffect: Fiber | null, // 子樹中第一個(gè)side effect firstEffect: Fiber | null, // 子樹中最后一個(gè)side effect lastEffect: Fiber | null, // 代表任務(wù)在未來的哪個(gè)時(shí)間點(diǎn)應(yīng)該被完成,之后版本改名為 lanes expirationTime: ExpirationTime, // 快速確定子樹中是否有不在等待的變化 childExpirationTime: ExpirationTime, // fiber的版本池,即記錄fiber更新過程,便于恢復(fù) alternate: Fiber | null, }
// 指向父級(jí)Fiber節(jié)點(diǎn) this.return = null; // 指向子Fiber節(jié)點(diǎn) this.child = null; // 指向右邊第一個(gè)兄弟Fiber節(jié)點(diǎn) this.sibling = null;
Fiber樹的遍歷是這樣發(fā)生的深度遍歷
開始:Fiber 從最上面的 React 元素開始遍歷,并為其創(chuàng)建一個(gè) fiber 節(jié)點(diǎn)。
子節(jié)點(diǎn):然后,它轉(zhuǎn)到子元素,為這個(gè)元素創(chuàng)建一個(gè) fiber 節(jié)點(diǎn)。這樣繼續(xù)下去直到在沒有孩子
兄弟節(jié)點(diǎn): 現(xiàn)在,它檢查是否有兄弟節(jié)點(diǎn)元素。如果有,它就遍歷兄弟節(jié)點(diǎn)元素,然后再到兄弟姐妹的葉子元素。
返回:如果沒有兄弟節(jié)點(diǎn),那么它就返回到父節(jié)點(diǎn)。
window.requestIdleCallback()
該方法將在瀏覽器的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì)。方法提供 deadline,即任務(wù)執(zhí)行限制時(shí)間,以切分任務(wù),避免長時(shí)間執(zhí)行,阻塞UI渲染而導(dǎo)致掉幀;
【安排低優(yōu)先級(jí)或非必要的函數(shù)在幀結(jié)束時(shí)的空閑時(shí)間被調(diào)用】
requestAnimationFrame
安排高優(yōu)先級(jí)的函數(shù)在下一個(gè)動(dòng)畫幀之前被調(diào)用
Fiber是如何工作的
- ReactDOM.render() 和 setState 的時(shí)候開始創(chuàng)建更新。
- 將創(chuàng)建的更新加入任務(wù)隊(duì)列,等待調(diào)度。
- 在 requestIdleCallback 空閑時(shí)執(zhí)行任務(wù)。
- 從根節(jié)點(diǎn)開始遍歷 Fiber Node,并且構(gòu)建 WokeInProgress Tree。
- 生成 effectList。
- 根據(jù) EffectList 更新 DOM。
當(dāng)調(diào)用render和setState方法進(jìn)行組件渲染和更新的時(shí)候,react會(huì)經(jīng)歷倆個(gè)階段:reconciler和render階段:
- 調(diào)和階段(Reconciler):官方解釋。React 會(huì)自頂向下通過遞歸,遍歷新數(shù)據(jù)生成新的 Virtual DOM,然后通過 Diff 算法,找到需要變更的元素(Patch),放到更新隊(duì)列里面去。
- 渲染階段(Renderer):遍歷更新隊(duì)列,通過調(diào)用宿主環(huán)境的API,實(shí)際更新渲染對(duì)應(yīng)元素。宿主環(huán)境,比如 DOM、Native、WebGL 等。
React15 最大的問題就是,Reconciler(協(xié)調(diào))階段產(chǎn)生產(chǎn)生虛擬DOM是通過深度優(yōu)先遞歸的,并且中途不可間斷。所以假如虛擬DOM很深的話,由于 JS線程和瀏覽器 GUI 線程是互斥的,處理 js 的時(shí)間過長,會(huì)導(dǎo)致瀏覽器刷新的時(shí)候掉幀,造成卡頓。
而 React16則實(shí)現(xiàn)了異步的可中斷的更新。
Fiber 使用 requestAnimationFrame 來處理優(yōu)先級(jí)較高的更新,使用 requestIdleCallback 處理優(yōu)先級(jí)較低的更新。因此,在調(diào)度工作時(shí),F(xiàn)iber 檢查當(dāng)前更新的優(yōu)先級(jí)和 deadline (幀結(jié)束后的自由時(shí)間)。
如果優(yōu)先級(jí)高于待處理的工作,或者沒有 截止日期 或者截止日期尚未到達(dá),F(xiàn)iber 可以在一幀之后安排多個(gè)工作單元。而下一組工作單元會(huì)被帶到更多的幀上。這就是使 Fiber 有可能暫停、重用和中止工作單元的原因。
那么,讓我們看看在預(yù)定的工作中實(shí)際發(fā)生了什么。有兩個(gè)階段來完成工作。render 和 commit。
渲染階段
實(shí)際的樹形遍歷和 deadline 的使用發(fā)生在這個(gè)階段。這是 Fiber 的內(nèi)部邏輯,所以在這個(gè)階段對(duì) Fiber 樹所做的改變對(duì)用戶來說是不可見的。因此,F(xiàn)iber 可以暫停、中止或分擔(dān)多個(gè)框架的工作。
我們可以把這個(gè)階段稱為協(xié)調(diào)階段。 fiber 從 fiber 樹的根部開始遍歷,處理每個(gè) fiber 。每一個(gè)工作單位都會(huì)調(diào)用workLoop 函數(shù)來執(zhí)行工作。我們可以把這個(gè)工作的處理分成兩個(gè)步驟。begin 和 complete 。
開始階段
如果你從 React 代碼庫中找到 workLoop 函數(shù),它就會(huì)調(diào)用 performUnitOfWork,它把 nextUnitOfWork 作為一個(gè)參數(shù),它就只是個(gè)工作的單位,將被執(zhí)行。 performUnitOfWork 函數(shù)內(nèi)部調(diào)用 beginWork 函數(shù)。這是 fiber 上發(fā)生實(shí)際工作的地方,而 performUnitOfWork 只是發(fā)生迭代的地方。
在 beginWork 函數(shù)中,如果 fiber 沒有任何待處理的工作,它就會(huì)直接跳出(跳過) fiber 而不進(jìn)入開始階段。這就是在遍歷大樹時(shí), fiber 跳過已經(jīng)處理過的 fiber ,直接跳到有待處理工作的 fiber 。如果你看到大的 beginWork 函數(shù)代碼塊,我們會(huì)發(fā)現(xiàn)一個(gè)開關(guān)塊,根據(jù) fiber 標(biāo)簽,調(diào)用相應(yīng)的 fiber 更新函數(shù)。就像 updateHostComponent 用于宿主組件。這些函數(shù)會(huì)更新 fiber 。
如果有子 fiber ,beginWork函數(shù)返回子 fiber ,如果沒有子 fiber 則返回空。函數(shù) performUnitOfWork 持續(xù)迭代并調(diào)用子 fiber ,直到葉節(jié)點(diǎn)到達(dá)。在葉子節(jié)點(diǎn)的情況下,beginWork 返回 null,因?yàn)闆]有任何子節(jié)點(diǎn),performUnitOfWork 函數(shù)調(diào)用 completeUnitOfWork 函數(shù)?,F(xiàn)在讓我們看看完善階段。
完善階段
這個(gè) completeUnitOfWork 函數(shù)通過調(diào)用一個(gè) completeWork 函數(shù)來完成當(dāng)前單位的工作。如果有的話,completeUnitOfWork 會(huì)返回一個(gè)同級(jí)的 fiber 來執(zhí)行下一個(gè)工作單元,如果沒有工作的話,則會(huì)完成 return(parent) fiber 。這將一直持續(xù)到返回值為空,也就是說,直到它到達(dá)根節(jié)點(diǎn)。和 beginWork 一樣,completeWork 也是一個(gè)發(fā)生實(shí)際工作的函數(shù),而 completeUnitOfWork 是用于迭代的。
渲染階段的結(jié)果會(huì)產(chǎn)生一個(gè)效果列表(副作用)。這些效果就像插入、更新或刪除宿主組件的節(jié)點(diǎn),或調(diào)用類組件節(jié)點(diǎn)的生命周期方法。這些 fiber 被標(biāo)記為各自的效果標(biāo)簽。
在渲染階段之后,F(xiàn)iber 將準(zhǔn)備提交更新。
提交階段
這是一個(gè)階段,完成的工作將被用來在用戶界面上渲染它。由于這一階段的結(jié)果對(duì)用戶來說是可見的,所以不能被分成部分渲染。這個(gè)階段是一個(gè)同步的階段。
在這個(gè)階段的開始,F(xiàn)iber 有已經(jīng)在 UI 上渲染的 current 樹,finishedWork,或者在渲染階段建立的 workInProgress 樹和效果列表。
effect 列表是 fiber 的鏈表,它有副作用。所以,它是渲染階段的 workInProgress 樹的節(jié)點(diǎn)的一個(gè)子集,它有副作用(更新)。effect 列表的節(jié)點(diǎn)是用 nextEffect 指針鏈接的。
在這個(gè)階段調(diào)用的函數(shù)是 completeRoot。
在這里,workInProgress 樹成為 current 樹,因?yàn)樗挥脕礓秩?UI。實(shí)際的 DOM 更新,如插入、更新、刪除,以及對(duì)生命周期方法的調(diào)用或者更新相對(duì)應(yīng)的引用 —— 發(fā)生在 effect 列表中的節(jié)點(diǎn)上。
這就是 fiber 協(xié)調(diào)器的工作方式。
結(jié)論
這就是 React Fiber 協(xié)調(diào)器使之有可能將工作分為多個(gè)工作單元。它設(shè)置每個(gè)工作的優(yōu)先級(jí),并使暫停、重用和中止工作單元成為可能。在 fiber 樹中,單個(gè)節(jié)點(diǎn)保持跟蹤,這是使上述事情成為可能的需要。每個(gè) fiber 都是一個(gè)鏈表的節(jié)點(diǎn),它們通過子、兄弟節(jié)點(diǎn)和返回引用連接起來。
- 有react fiber,為什么不需要vue fiber “我們現(xiàn)在已經(jīng)知道了react fiber是在彌補(bǔ)更新時(shí)“無腦”刷新,不夠精確帶來的缺陷。”fiber不是用來彌補(bǔ)無腦刷新的,fibe是用來讓原來同步的調(diào)用顆?;?,解決無腦刷新你應(yīng)該用meno
- vue不需要fiber是因?yàn)樗褂胣extTick來異步?jīng)Q定什么時(shí)候執(zhí)行renderfunction 本質(zhì)上思路是和react一致的和響應(yīng)式原理沒有半毛錢關(guān)系
到此這篇關(guān)于React超詳細(xì)講述Fiber的使用的文章就介紹到這了,更多相關(guān)React Fiber內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React學(xué)習(xí)筆記之高階組件應(yīng)用
這篇文章主要介紹了React 高階組件應(yīng)用,詳細(xì)的介紹了什么是React高階組件和具體使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06react實(shí)現(xiàn)移動(dòng)端二級(jí)路由嵌套詳解
這篇文章主要介紹了react移動(dòng)端二級(jí)路由嵌套的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08React useImperativeHandle處理組件狀態(tài)和生命周期用法詳解
React Hooks 為我們提供了一種全新的方式來處理組件的狀態(tài)和生命周期,useImperativeHandle是一個(gè)相對(duì)較少被提及的Hook,但在某些場景下,它是非常有用的,本文將深討useImperativeHandle的用法,并通過實(shí)例來加深理解2023-09-09使用Node搭建reactSSR服務(wù)端渲染架構(gòu)
這篇文章主要介紹了使用Node搭建reactSSR服務(wù)端渲染架構(gòu),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08每天一個(gè)hooks學(xué)習(xí)之useUnmount
這篇文章主要為大家介紹了每天一個(gè)hooks學(xué)習(xí)之useUnmount,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05