面試官:你是怎樣理解Fiber的
hello磁玉,這里是瀟晨,今天我們來聊一聊Fiber澈歉。不知道大家面試的時候有沒有遇到過和react Fiber相關(guān)的問題呢怎憋,這一類問題比較開放,但也是考察對react源碼理解深度的問題岸蜗,如果面試高級前端崗尉咕,恰巧你平時用的是react,那這道面試題是你必需要會的一道璃岳。
大型應(yīng)用為什么會慢
那之前的應(yīng)用為什么會慢呢年缎,傳統(tǒng)的前端應(yīng)用例如js原生或者jquery應(yīng)用悔捶,在構(gòu)建復(fù)雜的大型應(yīng)用的時候,各種頁面之前的相互操作和更新很有可能會引起頁面的重繪或重排列单芜,而頻繁操作這些dom其實是非常消耗性能的
在看下圖蜕该,這是一個節(jié)點(diǎn)上的屬性,可以看到一個節(jié)點(diǎn)上的屬性是非常多的洲鸠,在復(fù)雜應(yīng)用中堂淡,操作這些屬性的時候可能一不小心就會引起節(jié)點(diǎn)大量的更新,那如何提高應(yīng)用的性能呢扒腕?
const div = document.createElement('div');
let str = ''
for(let k in div){
str+=','+k
}
console.log(str)
為什么會出現(xiàn)Fiber
react從15版本開始绢淀,到現(xiàn)在的17,以及快出來的18袜匿,內(nèi)部經(jīng)歷了非常大的變化更啄,這一切都是圍繞著一個目標(biāo)進(jìn)行的,這個目標(biāo)是異步可中斷的更新居灯,而這個目的的最終結(jié)果是為了構(gòu)建快速響應(yīng)的應(yīng)用祭务。
復(fù)雜應(yīng)用在更新的時候可能會更新大量的dom,所以react在應(yīng)用層和dom層之間增加了一層Fiber怪嫌,而Fiber是在內(nèi)存中工作的义锥,所以在更新的時候只需要在內(nèi)存中進(jìn)行dom更新的比較,最后再應(yīng)用到需要更新真實節(jié)點(diǎn)上
這就引出了一個對比新老節(jié)點(diǎn)的過程岩灭,而對比兩棵樹的計算其實是非常消耗性能的拌倍,react提出了diff算法來降低對比的復(fù)雜度,具體diff的過程可以參考往期文章 diff算法
但是面對越來越復(fù)雜的應(yīng)用噪径,diff算法消耗的時間片還是很長柱恤,在沒做出優(yōu)化的情況下,react在進(jìn)行Fiber的對比和更新節(jié)點(diǎn)上的狀態(tài)的時候依然力不從心找爱,
- 在react15之前梗顺,這個對比的過程被稱之為stack reconcile,它的對比方式是‘一條路走到黑’车摄,也就是說這個對比的過程是不能被中斷的寺谤,這會出現(xiàn)什么情況呢,比如在頁面渲染一個比較消耗性能操作吮播,如果這個時候如果用戶進(jìn)行一些操作就會出現(xiàn)卡頓变屁,應(yīng)用就會顯得不流暢。
- react16之后出現(xiàn)了scheduler意狠,以及react17的Lane模型粟关,它們可以配合著工作,將比較耗時的任務(wù)按照Fiber節(jié)點(diǎn)劃分成工作單元环戈,并且遍歷Fiber樹計算或者更新節(jié)點(diǎn)上的狀態(tài)可以被中斷闷板、繼續(xù)获列,以及可以被高優(yōu)先級的任務(wù)打斷,比如用戶觸發(fā)的更新就是一個高優(yōu)先級的任務(wù)蛔垢,高優(yōu)先級的任務(wù)優(yōu)先執(zhí)行,應(yīng)用就不會太卡頓迫悠。
什么是Fiber
這就是react所要做的事情了鹏漆,react創(chuàng)新的提出了jsx,聲明式地描述頁面呈現(xiàn)的效果创泄,jsx會被babel經(jīng)過ast解析成React.createElement艺玲,而React.createElement函數(shù)執(zhí)行之后就是jsx對象或者說是virtual-dom
- 在mount的時候,也就是首次渲染的時候鞠抑,render階段會根據(jù)jsx對象生成新的Fiber節(jié)點(diǎn)饭聚,然后這些Fiber節(jié)點(diǎn)會被標(biāo)記成帶有‘Placement’的副作用,說明它們是新增的節(jié)點(diǎn)搁拙,需要被插入到真實節(jié)點(diǎn)中秒梳,在commit階段就會操作真實節(jié)點(diǎn),將它們插入到dom樹中箕速。
- 在update的時候酪碘,也就是應(yīng)用觸發(fā)更新的時候,render階段會根據(jù)最新的jsx和老的Fiber進(jìn)行對比盐茎,生成新的Fiber兴垦,這些Fiber會帶有各種副作用,比如‘Deletion’字柠、‘Update’探越、‘Placement’等,這一個對比的過程就是diff算法 窑业,在commit階段會操作真實節(jié)點(diǎn)钦幔,執(zhí)行相應(yīng)的副作用。
如果對render階段和commit階段不了解的可以查看往期文章
Fiber有比較多的含義数冬,他可以從以下幾個角度理解:
- 工作單元 任務(wù)分解 :Fiber最重要的功能就是作為工作單元节槐,保存原生節(jié)點(diǎn)或者組件節(jié)點(diǎn)對應(yīng)信息(包括優(yōu)先級),這些節(jié)點(diǎn)通過指針的形似形成Fiber樹
- 增量渲染:通過jsx對象和current Fiber的對比拐纱,生成最小的差異補(bǔ)丁铜异,應(yīng)用到真實節(jié)點(diǎn)上
- 根據(jù)優(yōu)先級暫停、繼續(xù)秸架、排列優(yōu)先級:Fiber節(jié)點(diǎn)上保存了優(yōu)先級揍庄,能通過不同節(jié)點(diǎn)優(yōu)先級的對比,達(dá)到任務(wù)的暫停东抹、繼續(xù)蚂子、排列優(yōu)先級等能力沃测,也為上層實現(xiàn)批量更新、Suspense提供了基礎(chǔ)
- 保存狀態(tài):因為Fiber能保存狀態(tài)和更新的信息食茎,所以就能實現(xiàn)函數(shù)組件的狀態(tài)更新蒂破,也就是hooks
Fiber的數(shù)據(jù)結(jié)構(gòu)
Fiber的自帶的屬性如下:
//ReactFiber.old.js
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
//作為靜態(tài)的數(shù)據(jù)結(jié)構(gòu) 保存節(jié)點(diǎn)的信息
this.tag = tag;//對應(yīng)組件的類型
this.key = key;//key屬性
this.elementType = null;//元素類型
this.type = null;//func或者class
this.stateNode = null;//真實dom節(jié)點(diǎn)
//作為fiber數(shù)架構(gòu) 連接成fiber樹
this.return = null;//指向父節(jié)點(diǎn)
this.child = null;//指向child
this.sibling = null;//指向兄弟節(jié)點(diǎn)
this.index = 0;
this.ref = null;
//用作為工作單元 來計算state
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
//effect相關(guān)
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
//優(yōu)先級相關(guān)的屬性
this.lanes = NoLanes;
this.childLanes = NoLanes;
//current和workInProgress的指針
this.alternate = null;
}
Fiber是怎樣工作的
現(xiàn)在我們知道了Fiber可以保存真實的dom,真實dom對應(yīng)在內(nèi)存中的Fiber節(jié)點(diǎn)會形成Fiber樹别渔,這顆Fiber樹在react中叫current Fiber附迷,也就是當(dāng)前dom樹對應(yīng)的Fiber樹,而正在構(gòu)建Fiber樹叫workInProgress Fiber哎媚,這兩顆樹的節(jié)點(diǎn)通過alternate相連.
function App() {
return (
<>
<h1>
<p>count</p> xiaochen
</h1>
</>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
構(gòu)建workInProgress Fiber發(fā)生在createWorkInProgress中喇伯,它能創(chuàng)建或者服用Fiber
//ReactFiber.old.js
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {//區(qū)分是在mount時還是在update時
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;//復(fù)用屬性
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
//...
}
workInProgress.childLanes = current.childLanes;//復(fù)用屬性
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
return workInProgress;
}
-
在mount時:會創(chuàng)建fiberRoot和rootFiber,然后根據(jù)jsx對象創(chuàng)建Fiber節(jié)點(diǎn)拨与,節(jié)點(diǎn)連接成current Fiber樹稻据。
-
在update時:會根據(jù)新的狀態(tài)形成的jsx(ClassComponent的render或者FuncComponent的返回值)和current Fiber對比形(diff算法)成一顆叫workInProgress的Fiber樹,然后將fiberRoot的current指向workInProgress樹买喧,此時workInProgress就變成了current Fiber捻悯。fiberRoot:指整個應(yīng)用的根節(jié)點(diǎn),只存在一個
fiberRoot:指整個應(yīng)用的根節(jié)點(diǎn)岗喉,只存在一個
rootFiber:ReactDOM.render或者ReactDOM.unstable_createRoot創(chuàng)建出來的應(yīng)用的節(jié)點(diǎn)秋度,可以存在多個。
我們現(xiàn)在知道了存在current Fiber和workInProgress Fiber兩顆Fiber樹钱床,F(xiàn)iber雙緩存指的就是荚斯,在經(jīng)過reconcile(diff)形成了新的workInProgress Fiber然后將workInProgress Fiber切換成current Fiber應(yīng)用到真實dom中,存在雙Fiber的好處是在內(nèi)存中形成視圖的描述查牌,在最后應(yīng)用到dom中事期,減少了對dom的操作。
現(xiàn)在來看看Fiber雙緩存創(chuàng)建的過程圖:
-
mount時:
-
剛開始只創(chuàng)建了fiberRoot和rootFiber兩個節(jié)點(diǎn)
-
然后根據(jù)jsx創(chuàng)建workInProgress Fiber:
-
把workInProgress Fiber切換成current Fiber
-
-
update時
-
根據(jù)current Fiber創(chuàng)建workInProgress Fiber
- 把workInProgress Fiber切換成current Fiber
-
為什么Fiber能提升效率
Fiber是一個js對象纸颜,能承載節(jié)點(diǎn)信息兽泣、優(yōu)先級、updateQueue胁孙,同時它還是一個工作單元唠倦。
- Fiber雙緩存可以在構(gòu)建好wip Fiber樹之后切換成current Fiber,內(nèi)存中直接一次性切換涮较,提高了性能
- Fiber的存在使異步可中斷的更新成為了可能稠鼻,作為工作單元,可以在時間片內(nèi)執(zhí)行工作狂票,沒時間了交還執(zhí)行權(quán)給瀏覽器候齿,下次時間片繼續(xù)執(zhí)行之前暫停之后返回的Fiber
- Fiber可以在reconcile的時候進(jìn)行相應(yīng)的diff更新,讓最后的更新應(yīng)用在真實節(jié)點(diǎn)上