面試官:你是怎樣理解Fiber的

面試官:你是怎樣理解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其實是非常消耗性能的

react源碼5.1

在看下圖蜕该,這是一個節(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)
react源碼5.2

為什么會出現(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階段不了解的可以查看往期文章

8.render階段

10.commit階段

react源碼3.1

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"));
react源碼7.2

構(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樹稻据。


    react源碼7.1
  • 在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時:

    1. 剛開始只創(chuàng)建了fiberRoot和rootFiber兩個節(jié)點(diǎn)


      react源碼7.6
    2. 然后根據(jù)jsx創(chuàng)建workInProgress Fiber:


      react源碼7.7
    3. 把workInProgress Fiber切換成current Fiber


      react源碼7.8
  • update時

    1. 根據(jù)current Fiber創(chuàng)建workInProgress Fiber


      react源碼7.9
    2. 把workInProgress Fiber切換成current Fiber
react源碼7.8

為什么Fiber能提升效率

Fiber是一個js對象纸颜,能承載節(jié)點(diǎn)信息兽泣、優(yōu)先級、updateQueue胁孙,同時它還是一個工作單元唠倦。

  1. Fiber雙緩存可以在構(gòu)建好wip Fiber樹之后切換成current Fiber,內(nèi)存中直接一次性切換涮较,提高了性能
  2. Fiber的存在使異步可中斷的更新成為了可能稠鼻,作為工作單元,可以在時間片內(nèi)執(zhí)行工作狂票,沒時間了交還執(zhí)行權(quán)給瀏覽器候齿,下次時間片繼續(xù)執(zhí)行之前暫停之后返回的Fiber
  3. Fiber可以在reconcile的時候進(jìn)行相應(yīng)的diff更新,讓最后的更新應(yīng)用在真實節(jié)點(diǎn)上

視頻講解(高效學(xué)習(xí)):點(diǎn)擊學(xué)習(xí)

往期react源碼解析文章:

1.開篇介紹和面試題

2.react的設(shè)計理念

3.react源碼架構(gòu)

4.源碼目錄結(jié)構(gòu)和調(diào)試

5.jsx&核心api

6.legacy和concurrent模式入口函數(shù)

7.Fiber架構(gòu)

8.render階段

9.diff算法

10.commit階段

11.生命周期

12.狀態(tài)更新流程

13.hooks源碼

14.手寫hooks

15.scheduler&Lane

16.concurrent模式

17.context

18事件系統(tǒng)

19.手寫迷你版react

20.總結(jié)&第一章的面試題解答

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市慌盯,隨后出現(xiàn)的幾起案子周霉,更是在濱河造成了極大的恐慌,老刑警劉巖亚皂,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俱箱,死亡現(xiàn)場離奇詭異,居然都是意外死亡灭必,警方通過查閱死者的電腦和手機(jī)匠楚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來厂财,“玉大人,你說我怎么就攤上這事峡懈×Пィ” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵肪康,是天一觀的道長荚恶。 經(jīng)常有香客問我,道長磷支,這世上最難降的妖魔是什么谒撼? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮雾狈,結(jié)果婚禮上廓潜,老公的妹妹穿的比我還像新娘。我一直安慰自己善榛,他們只是感情好辩蛋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著移盆,像睡著了一般悼院。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咒循,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天据途,我揣著相機(jī)與錄音,去河邊找鬼叙甸。 笑死颖医,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚁署。 我是一名探鬼主播便脊,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了哪痰?” 一聲冷哼從身側(cè)響起遂赠,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晌杰,沒想到半個月后跷睦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肋演,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年抑诸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爹殊。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜕乡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梗夸,到底是詐尸還是另有隱情层玲,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布反症,位于F島的核電站辛块,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铅碍。R本人自食惡果不足惜润绵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胞谈。 院中可真熱鬧尘盼,春花似錦、人聲如沸烦绳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爵嗅。三九已至娇澎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間睹晒,已是汗流浹背趟庄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伪很,地道東北人戚啥。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像锉试,于是被迫代替她去往敵國和親猫十。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容