網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
Fiber
概念
JavaScript引擎和頁(yè)面渲染引擎兩個(gè)線程是互斥的,當(dāng)其中一個(gè)線程執(zhí)行時(shí),另一個(gè)線程只能掛起等待
如果 JavaScript 線程長(zhǎng)時(shí)間地占用了主線程,那么渲染層面的更新就不得不長(zhǎng)時(shí)間地等待,界面長(zhǎng)時(shí)間不更新,會(huì)導(dǎo)致頁(yè)面響應(yīng)度變差,用戶可能會(huì)感覺(jué)到卡頓
破解JavaScript中同步操作時(shí)間過(guò)長(zhǎng)的方法其實(shí)很簡(jiǎn)單——分片。
把一個(gè)耗時(shí)長(zhǎng)的任務(wù)分成很多小片,每一個(gè)小片的運(yùn)行時(shí)間很短,雖然總時(shí)間依然很長(zhǎng),但是在每個(gè)小片執(zhí)行完之后,都給其他任務(wù)一個(gè)執(zhí)行的機(jī)會(huì),這樣唯一的線程就不會(huì)被獨(dú)占,其他任務(wù)依然有運(yùn)行的機(jī)會(huì)。
React Fiber把更新過(guò)程碎片化,每執(zhí)行完一段更新過(guò)程,就把控制權(quán)交還給React負(fù)責(zé)任務(wù)協(xié)調(diào)的模塊,看看有沒(méi)有其他緊急任務(wù)要做,如果沒(méi)有就繼續(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樹(shù)變成了鏈表,一個(gè)dom對(duì)應(yīng)兩個(gè)fiber(一個(gè)鏈表),對(duì)應(yīng)兩個(gè)隊(duì)列,這都是為找到被中斷的任務(wù),重新執(zhí)行
從架構(gòu)角度來(lái)看,F(xiàn)iber 是對(duì) React核心算法(即調(diào)和過(guò)程)的重寫(xiě)
從編碼角度來(lái)看,F(xiàn)iber是 React內(nèi)部所定義的一種數(shù)據(jù)結(jié)構(gòu),它是 Fiber樹(shù)結(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)樹(shù)中的`parent`,用來(lái)在處理完這個(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)前處理過(guò)程中的組件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
// 用來(lái)記錄Side Effect
effectTag: SideEffectTag,
// 單鏈表用來(lái)快速查找下一個(gè)side effect
nextEffect: Fiber | null,
// 子樹(shù)中第一個(gè)side effect
firstEffect: Fiber | null,
// 子樹(shù)中最后一個(gè)side effect
lastEffect: Fiber | null,
// 代表任務(wù)在未來(lái)的哪個(gè)時(shí)間點(diǎn)應(yīng)該被完成,之后版本改名為 lanes
expirationTime: ExpirationTime,
// 快速確定子樹(shù)中是否有不在等待的變化
childExpirationTime: ExpirationTime,
// fiber的版本池,即記錄fiber更新過(guò)程,便于恢復(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樹(shù)的遍歷是這樣發(fā)生的深度遍歷
開(kāi)始:Fiber 從最上面的 React 元素開(kāi)始遍歷,并為其創(chuàng)建一個(gè) fiber 節(jié)點(diǎn)。
子節(jié)點(diǎn):然后,它轉(zhuǎn)到子元素,為這個(gè)元素創(chuàng)建一個(gè) fiber 節(jié)點(diǎn)。這樣繼續(xù)下去直到在沒(méi)有孩子
兄弟節(jié)點(diǎn): 現(xiàn)在,它檢查是否有兄弟節(jié)點(diǎn)元素。如果有,它就遍歷兄弟節(jié)點(diǎn)元素,然后再到兄弟姐妹的葉子元素。
返回:如果沒(méi)有兄弟節(jié)點(diǎn),那么它就返回到父節(jié)點(diǎn)。
window.requestIdleCallback()
該方法將在瀏覽器的空閑時(shí)段內(nèi)調(diào)用的函數(shù)排隊(duì)。方法提供 deadline,即任務(wù)執(zhí)行限制時(shí)間,以切分任務(wù),避免長(zhǎng)時(shí)間執(zhí)行,阻塞UI渲染而導(dǎo)致掉幀;
【安排低優(yōu)先級(jí)或非必要的函數(shù)在幀結(jié)束時(shí)的空閑時(shí)間被調(diào)用】
requestAnimationFrame
安排高優(yōu)先級(jí)的函數(shù)在下一個(gè)動(dòng)畫(huà)幀之前被調(diào)用
Fiber是如何工作的
- ReactDOM.render() 和 setState 的時(shí)候開(kāi)始創(chuàng)建更新。
- 將創(chuàng)建的更新加入任務(wù)隊(duì)列,等待調(diào)度。
- 在 requestIdleCallback 空閑時(shí)執(zhí)行任務(wù)。
- 從根節(jié)點(diǎn)開(kāi)始遍歷 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ì)自頂向下通過(guò)遞歸,遍歷新數(shù)據(jù)生成新的 Virtual DOM,然后通過(guò) Diff 算法,找到需要變更的元素(Patch),放到更新隊(duì)列里面去。
- 渲染階段(Renderer):遍歷更新隊(duì)列,通過(guò)調(diào)用宿主環(huán)境的API,實(shí)際更新渲染對(duì)應(yīng)元素。宿主環(huán)境,比如 DOM、Native、WebGL 等。
React15 最大的問(wèn)題就是,Reconciler(協(xié)調(diào))階段產(chǎn)生產(chǎn)生虛擬DOM是通過(guò)深度優(yōu)先遞歸的,并且中途不可間斷。所以假如虛擬DOM很深的話,由于 JS線程和瀏覽器 GUI 線程是互斥的,處理 js 的時(shí)間過(guò)長(zhǎng),會(huì)導(dǎo)致瀏覽器刷新的時(shí)候掉幀,造成卡頓。
而 React16則實(shí)現(xiàn)了異步的可中斷的更新。
Fiber 使用 requestAnimationFrame 來(lái)處理優(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í)高于待處理的工作,或者沒(méi)有 截止日期 或者截止日期尚未到達(dá),F(xiàn)iber 可以在一幀之后安排多個(gè)工作單元。而下一組工作單元會(huì)被帶到更多的幀上。這就是使 Fiber 有可能暫停、重用和中止工作單元的原因。
那么,讓我們看看在預(yù)定的工作中實(shí)際發(fā)生了什么。有兩個(gè)階段來(lái)完成工作。render 和 commit。
渲染階段
實(shí)際的樹(shù)形遍歷和 deadline 的使用發(fā)生在這個(gè)階段。這是 Fiber 的內(nèi)部邏輯,所以在這個(gè)階段對(duì) Fiber 樹(shù)所做的改變對(duì)用戶來(lái)說(shuō)是不可見(jiàn)的。因此,F(xiàn)iber 可以暫停、中止或分擔(dān)多個(gè)框架的工作。
我們可以把這個(gè)階段稱為協(xié)調(diào)階段。 fiber 從 fiber 樹(shù)的根部開(kāi)始遍歷,處理每個(gè) fiber 。每一個(gè)工作單位都會(huì)調(diào)用workLoop 函數(shù)來(lái)執(zhí)行工作。我們可以把這個(gè)工作的處理分成兩個(gè)步驟。begin 和 complete 。
開(kāi)始階段
如果你從 React 代碼庫(kù)中找到 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 沒(méi)有任何待處理的工作,它就會(huì)直接跳出(跳過(guò)) fiber 而不進(jìn)入開(kāi)始階段。這就是在遍歷大樹(shù)時(shí), fiber 跳過(guò)已經(jīng)處理過(guò)的 fiber ,直接跳到有待處理工作的 fiber 。如果你看到大的 beginWork 函數(shù)代碼塊,我們會(huì)發(fā)現(xiàn)一個(gè)開(kāi)關(guān)塊,根據(jù) fiber 標(biāo)簽,調(diào)用相應(yīng)的 fiber 更新函數(shù)。就像 updateHostComponent 用于宿主組件。這些函數(shù)會(huì)更新 fiber 。
如果有子 fiber ,beginWork函數(shù)返回子 fiber ,如果沒(méi)有子 fiber 則返回空。函數(shù) performUnitOfWork 持續(xù)迭代并調(diào)用子 fiber ,直到葉節(jié)點(diǎn)到達(dá)。在葉子節(jié)點(diǎn)的情況下,beginWork 返回 null,因?yàn)闆](méi)有任何子節(jié)點(diǎn),performUnitOfWork 函數(shù)調(diào)用 completeUnitOfWork 函數(shù)。現(xiàn)在讓我們看看完善階段。
完善階段
這個(gè) completeUnitOfWork 函數(shù)通過(guò)調(diào)用一個(gè) completeWork 函數(shù)來(lái)完成當(dāng)前單位的工作。如果有的話,completeUnitOfWork 會(huì)返回一個(gè)同級(jí)的 fiber 來(lái)執(zhí)行下一個(gè)工作單元,如果沒(méi)有工作的話,則會(huì)完成 return(parent) fiber 。這將一直持續(xù)到返回值為空,也就是說(shuō),直到它到達(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è)階段,完成的工作將被用來(lái)在用戶界面上渲染它。由于這一階段的結(jié)果對(duì)用戶來(lái)說(shuō)是可見(jiàn)的,所以不能被分成部分渲染。這個(gè)階段是一個(gè)同步的階段。
在這個(gè)階段的開(kāi)始,F(xiàn)iber 有已經(jīng)在 UI 上渲染的 current 樹(shù),finishedWork,或者在渲染階段建立的 workInProgress 樹(shù)和效果列表。
effect 列表是 fiber 的鏈表,它有副作用。所以,它是渲染階段的 workInProgress 樹(shù)的節(jié)點(diǎn)的一個(gè)子集,它有副作用(更新)。effect 列表的節(jié)點(diǎn)是用 nextEffect 指針鏈接的。
在這個(gè)階段調(diào)用的函數(shù)是 completeRoot。
在這里,workInProgress 樹(shù)成為 current 樹(shù),因?yàn)樗挥脕?lái)渲染 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 樹(shù)中,單個(gè)節(jié)點(diǎn)保持跟蹤,這是使上述事情成為可能的需要。每個(gè) fiber 都是一個(gè)鏈表的節(jié)點(diǎn),它們通過(guò)子、兄弟節(jié)點(diǎn)和返回引用連接起來(lái)。
- 有react fiber,為什么不需要vue fiber “我們現(xiàn)在已經(jīng)知道了react fiber是在彌補(bǔ)更新時(shí)“無(wú)腦”刷新,不夠精確帶來(lái)的缺陷。”fiber不是用來(lái)彌補(bǔ)無(wú)腦刷新的,fibe是用來(lái)讓原來(lái)同步的調(diào)用顆粒化的,解決無(wú)腦刷新你應(yīng)該用meno
- vue不需要fiber是因?yàn)樗褂胣extTick來(lái)異步?jīng)Q定什么時(shí)候執(zhí)行renderfunction 本質(zhì)上思路是和react一致的和響應(yīng)式原理沒(méi)有半毛錢(qián)關(guān)系
原文鏈接:https://blog.csdn.net/weixin_42910765/article/details/128832862
相關(guān)推薦
- 2022-11-28 詳解Pytorch如何利用yaml定義卷積網(wǎng)絡(luò)_python
- 2022-10-10 React?組件的常用生命周期函數(shù)匯總_Redis
- 2022-03-27 C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)詳解_C 語(yǔ)言
- 2022-11-16 python3中requests庫(kù)重定向獲取URL_python
- 2022-07-24 .Net行為型設(shè)計(jì)模式之中介者模式(Mediator)_基礎(chǔ)應(yīng)用
- 2022-11-25 詳解React中Fragment的簡(jiǎn)單使用_React
- 2022-07-08 python中的annotate函數(shù)使用_python
- 2022-10-12 字節(jié)封裝React組件手機(jī)號(hào)自動(dòng)校驗(yàn)格式FormItem_React
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門(mén)
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支