一篇帶你了解 React Fiber 是什么?
大家好,我是前端西瓜哥。
為了提高 React 的性能,React 團(tuán)隊(duì)在開發(fā) React 16 時(shí)做了底層的重構(gòu),引入了 React Fiber 的概念。
React Fiber 是什么?
Fiber,本意為 “纖維”,在計(jì)算機(jī)世界中則是 ”纖程“ 的意思。纖程可以看作是協(xié)程的一種,是一種任務(wù)調(diào)度方式。
JavaScript 是單線程的,有一個(gè) event loop 的概念,它有一個(gè)有優(yōu)先級的任務(wù)隊(duì)列,只能按順序執(zhí)行一個(gè)任務(wù),是不支持多個(gè)任務(wù)同時(shí)執(zhí)行的。
這種設(shè)計(jì)的好處就是不用考慮多線程導(dǎo)致的順序問題,并為此做一些加鎖的額外邏輯,確保執(zhí)行順序符合預(yù)期。但也因?yàn)闊o法使用并行能力,在 CPU 密集的場景會有性能問題, 比如一個(gè)任務(wù)耗時(shí)過長會導(dǎo)致其他的任務(wù),導(dǎo)致用戶的交互響應(yīng)發(fā)生延遲。
?React 的組件更新是 CPU 密集的操作,因?yàn)樗鰧Ρ刃屡f虛擬 DOM 樹的操作(diff,React 中 Reconcilation 負(fù)責(zé)),找出需要更新的內(nèi)容(patch),通過打補(bǔ)丁的方式更新真實(shí) DOM 樹(React 中 Renderer 負(fù)責(zé))。當(dāng)要對比的組件樹非常多時(shí),就會發(fā)生大量的新舊節(jié)點(diǎn)對比,CPU 花費(fèi)時(shí)間龐大,當(dāng)耗時(shí)大大超過 16.6ms(一秒 60 幀的基準(zhǔn)) 時(shí),用戶會感覺到明顯的卡頓。
這一系列操作是通過遞歸的方式實(shí)現(xiàn)的,是 同步且不可中斷 的。因?yàn)橐坏┲袛?,調(diào)用棧就會被銷毀,中間的狀態(tài)就丟失了。這種基于調(diào)用棧的實(shí)現(xiàn),我們稱為 Stack Reconcilation。
React 16 的一個(gè)重點(diǎn)工作就是優(yōu)化更新組件時(shí)大量的 CPU 計(jì)算,最后選擇了使用 “時(shí)間分片” 的方案,就是將原本要一次性做的工作,拆分成一個(gè)個(gè)異步任務(wù),在瀏覽器空閑的時(shí)間時(shí)執(zhí)行。這種新的架構(gòu)稱為 Fiber Reconcilation。
在 React 中,F(xiàn)iber 模擬之前的遞歸調(diào)用,具體通過鏈表的方式去模擬函數(shù)的調(diào)用棧,這樣就可以做到中斷調(diào)用,將一個(gè)大的更新任務(wù),拆分成小的任務(wù),并設(shè)置優(yōu)先級,在瀏覽器空閑的時(shí)異步執(zhí)行。
FiberNode
前面我們說到使用了鏈表的遍歷來模擬遞歸棧調(diào)用,其中鏈表的節(jié)點(diǎn) React 用 FiberNode 表示。
FiberNode 其實(shí)就是虛擬 DOM,它記錄了:
- 節(jié)點(diǎn)相關(guān)類型,比如 tag 表示組件類型、type 表示元素類型等。
- 節(jié)點(diǎn)的指向。
- 副作用相關(guān)的屬性。
- lanes 是關(guān)于調(diào)度優(yōu)先級的。
Fiber 通過 return 指向父 Fiber,child 指向子 Fiber 的首位、sibling 指向下一個(gè)兄弟節(jié)點(diǎn)。通過它們我們其實(shí)就能拿到一個(gè)完整的結(jié)構(gòu)樹。
對于:
形成的 Fiber 樹為:
其中弧線為調(diào)用順序。紫色為 beginWork、粉色為 completeWork。beginWork 是 “遞” 的過程,而 comleteWork 則是 “歸” 的過程。
為什么不用 generator 或 async/await?
generator 和 async/await 也可以做到在函數(shù)中間暫停函數(shù)執(zhí)行的邏輯,將執(zhí)行讓出去,能做到將同步變成異步。
但 React 沒有選擇它們,這是因?yàn)椋?/p>
- 具有傳染性,比如一個(gè)函數(shù)用了 async,調(diào)用它的函數(shù)就要加上 async,有語法開銷,此外也會有性能上的額外開銷。
- 無法在 generator 和 async/await 中恢復(fù)一些中間狀態(tài)。
具體見官方的 github issue 討論:
https://github.com/facebook/react/issues/7942#issuecomment-254987818。
Scheduler
做了時(shí)間分片,拆分了多個(gè)任務(wù),React 就可以以此為基石,給任務(wù)設(shè)置優(yōu)先級。
React 實(shí)現(xiàn)了一個(gè) Scheduler(調(diào)度器)來實(shí)現(xiàn)任務(wù)調(diào)度執(zhí)行,并單獨(dú)抽離為一個(gè)單獨(dú)的包,它會在瀏覽器有空閑的時(shí)候執(zhí)行。其實(shí)瀏覽器也提供了一個(gè) requestIdleCallback 的 API,支持這個(gè)能力,但兼容性實(shí)在不好,React 還是自己實(shí)現(xiàn)了一套。
這個(gè) Scheduler 支持優(yōu)先級,底層使用了 小頂堆,確保能高效拿到最快要過期的任務(wù),然后執(zhí)行它。
小頂堆,其實(shí)就是優(yōu)先級隊(duì)列。小頂堆在結(jié)構(gòu)上是一個(gè)完全二叉樹,但能保證每次從堆頂取出元素時(shí),是最小的元素。
任務(wù)的 優(yōu)先級 分為幾種:
- NoPriority:無優(yōu)先級。
- ImmediatePriority:立即執(zhí)行。
- UserBlockingPriority:用戶阻塞優(yōu)先級,不執(zhí)行可能會導(dǎo)致用戶交互阻塞。
- NormalPriority:普通優(yōu)先級。
- LowPriority:低優(yōu)先級。
- IdlePriority:空閑優(yōu)先級。
React 自身也有優(yōu)先級,叫做 Lane,兩者是不同的。
結(jié)尾
React 的架構(gòu)過于宏大,今天先隨便說一點(diǎn)吧。
總的來說,React Fiber 是在 React 16 中引入的新的架構(gòu),將原本同步不可中斷的更新,變成異步可中斷更新,將原本一個(gè)耗時(shí)的大任務(wù)做了時(shí)間分片,拆分成一個(gè)個(gè)小任務(wù),在瀏覽器空閑的時(shí)間執(zhí)行。此外添加優(yōu)先級的概念,將一些重要的任務(wù)先執(zhí)行,比如一些用戶交互的響應(yīng)函數(shù)。
一切為了更好的用戶體驗(yàn)。