React為什么要推出Fiber架構(gòu)?
在頁(yè)面元素很多,還需要頻繁刷新的情況下,React15會(huì)出現(xiàn)掉幀的現(xiàn)象。其根本原因是,大量的同步計(jì)算任務(wù)阻塞了瀏覽器的UI渲染。默認(rèn)情況下,JS 運(yùn)算、頁(yè)面布局和頁(yè)面繪制都是運(yùn)行在瀏覽器的主線程當(dāng)中,他們之間是互斥的關(guān)系。如果 JS 運(yùn)算持續(xù)占用主線程,頁(yè)面就沒(méi)法得到及時(shí)的更新。當(dāng)我們調(diào)用setState更新頁(yè)面的時(shí)候,React 會(huì)遍歷應(yīng)用的所有節(jié)點(diǎn),計(jì)算出差異,然后再更新 UI。整個(gè)過(guò)程是一氣呵成,不能被打斷的。如果頁(yè)面元素很多,整個(gè)過(guò)程占用的時(shí)機(jī)就可能超過(guò) 16 毫秒,就容易出現(xiàn)掉幀的現(xiàn)象。
React16提出的解決方案
解決主線程長(zhǎng)時(shí)間被 JS 運(yùn)算占用這一問(wèn)題的基本思路,是將運(yùn)算切割為多個(gè)步驟,分批完成。也就是說(shuō)在完成一部分任務(wù)之后,將控制權(quán)交回給瀏覽器,讓瀏覽器有時(shí)間進(jìn)行頁(yè)面的渲染。等瀏覽器忙完之后,再繼續(xù)之前未完成的任務(wù)。
舊版本的React是通過(guò)遞歸的方式進(jìn)行渲染,使用的是JS引擎自身的函數(shù)調(diào)用棧,它會(huì)一直執(zhí)行到??諡橹?。而Fiber實(shí)現(xiàn)了自己的組件調(diào)用棧,它以鏈表的形式遍歷組件樹(shù),可以靈活的暫停、繼續(xù)和丟棄執(zhí)行的任務(wù)。實(shí)現(xiàn)方式是使用了瀏覽器的requestIdleCallback這一 API。
Fiber的框架可分為三層:
- Virtual DOM 層,描述頁(yè)面長(zhǎng)什么樣。
- Reconciler 層,負(fù)責(zé)調(diào)用組件生命周期方法,進(jìn)行 Diff 運(yùn)算等。
- Renderer 層,根據(jù)不同的平臺(tái),渲染出相應(yīng)的頁(yè)面,比較常見(jiàn)的是 ReactDOM 和 ReactNative。
主要改動(dòng)在Reconciler 層,即Fiber。Fiber Reconciler 每執(zhí)行一段時(shí)間,都會(huì)將控制權(quán)交回給瀏覽器。而Fiber Reconciler在執(zhí)行過(guò)程中分兩個(gè)階段:
- 階段一,生成 Fiber 樹(shù),得出需要更新的節(jié)點(diǎn)信息。這一步是一個(gè)漸進(jìn)的過(guò)程,可以被打斷。
- 階段二,將需要更新的節(jié)點(diǎn)一次過(guò)批量更新,這個(gè)過(guò)程不能被打斷。
階段一可被打斷的特性,讓優(yōu)先級(jí)更高的任務(wù)先執(zhí)行,從框架層面大大降低了頁(yè)面掉幀的概率。
Fiber 樹(shù)在首次渲染的時(shí)候會(huì)一次過(guò)生成。在后續(xù)需要 Diff 的時(shí)候,會(huì)根據(jù)已有樹(shù)和最新 Virtual DOM 的信息,生成一棵新的樹(shù)。這顆新樹(shù)每生成一個(gè)新的節(jié)點(diǎn),都會(huì)將控制權(quán)交回給主線程,去檢查有沒(méi)有優(yōu)先級(jí)更高的任務(wù)需要執(zhí)行。如果沒(méi)有,則繼續(xù)構(gòu)建樹(shù)的過(guò)程。如果過(guò)程中有優(yōu)先級(jí)更高的任務(wù)需要進(jìn)行,則 Fiber Reconciler 會(huì)丟棄正在生成的樹(shù),在空閑的時(shí)候再重新執(zhí)行一遍。
在構(gòu)造 Fiber 樹(shù)的過(guò)程中,F(xiàn)iber Reconciler 會(huì)將需要更新的節(jié)點(diǎn)信息保存在Effect List當(dāng)中,在階段二執(zhí)行的時(shí)候,會(huì)批量更新相應(yīng)的節(jié)點(diǎn)。
Fiber核心是實(shí)現(xiàn)了一個(gè)基于優(yōu)先級(jí)和requestIdleCallback的循環(huán)任務(wù)調(diào)度算法。
參考文章:React Fiber 原理介紹