App下載

你也可以理解的React Fiber,學(xué)廢了嗎

猿友 2020-09-24 14:57:09 瀏覽數(shù) (5285)
反饋

文章來源于公眾號:前端時光屋 作者:小豪

Fiber出現(xiàn)的背景?

在早期的 React 版本中,也就是 React16.8 版本之前。

大量的同步計算任務(wù)阻塞了瀏覽器的UI渲染。默認(rèn)情況下,JS運算、頁面布局頁面繪制渲染都是運行在瀏覽器的主線程當(dāng)中,他們之間是互斥的關(guān)系。

如果 JS 運算持續(xù)占用主線程,頁面就沒法得到及時的更新,當(dāng)我們調(diào)用setState更新頁面的時候,React 會遍歷應(yīng)用的所有節(jié)點,與老的 dom 節(jié)點進(jìn)行 diff 算法的對比,最小代價更新頁面,即使這樣,整個過程也是一氣呵成,不能被打斷的,如果頁面元素很多,整個過程占用的時間就可能超過16毫秒,出現(xiàn)掉幀的現(xiàn)象。

針對這一現(xiàn)象,React 團隊從框架層面對 web 頁面的運行機制做了優(yōu)化,此后,Fiber誕生了。

說到16ms,我們來看這樣的一個概念

屏幕刷新率

  • 目前大多數(shù)設(shè)備的屏幕刷新率為60次/秒
  • 瀏覽器的渲染動畫或頁面的每一幀的速率也需要跟設(shè)備屏幕的刷新率保持一致。
  • 頁面是一幀一幀繪制出來的,當(dāng)每秒繪制的幀數(shù)(FPS)達(dá)到60時,頁面是流暢的,小于這個值時,用戶會感覺到卡頓。
  • 每個幀的預(yù)算時間是16.66毫秒(1秒/60)
  • 1s 60幀,所以我們書寫代碼時盡量不讓一幀的工作量超過16ms

Fiber的誕生

解決主線程長時間被 JS 暈眩占用這一問題的基本思路,是將運算切割為多個步驟,分批完成。也就是說在完成一部分任務(wù)之后, 將控制權(quán)交回給瀏覽器,讓瀏覽器有時間再進(jìn)行頁面的渲染。等瀏覽器忙完之后,再繼續(xù)之前React未完成的任務(wù)。

舊版 React 通過遞歸的方式進(jìn)行渲染,使用的是 JS 引擎自身的函數(shù)調(diào)用棧,它會一直執(zhí)行到??諡橹?/strong>。

Fiber實現(xiàn)了自己的組件調(diào)用棧,它以鏈表的形式遍歷組件樹,可以靈活地暫停、繼續(xù)和丟棄執(zhí)行的任務(wù)。實現(xiàn)的方式是使用了 瀏覽器的requestIdleCallback這一 API。官方的解釋是這樣的:

window.requestIdleCallback()會在瀏覽器空閑時期依次調(diào)用函數(shù),這就可以讓開發(fā)者在主事件循環(huán)中執(zhí)行后臺優(yōu)先級低的任務(wù),而且不會像對動畫和用戶交互這些延遲觸發(fā)產(chǎn)生關(guān)鍵的事件影響。函數(shù)一般會按先進(jìn)先調(diào)用的順序執(zhí)行,除非函數(shù)在瀏覽器調(diào)用它之前就到了它的超時時間。

requestIdleCallback的核心用法

  • 希望快速響應(yīng)用戶,讓用戶覺得夠快,不能阻塞用戶的交互行為
  • requestIdleCallback 使開發(fā)者能夠在主事件循環(huán)上執(zhí)行后臺和低優(yōu)先級的工作,而不會影響延遲關(guān)鍵事件,例如動畫和輸入的響應(yīng)
  • 正常幀任務(wù)完成后沒超過16ms,說明時間有賦予,此時就會執(zhí)行requestIdleCallback里注冊的任務(wù)

requestIdleCallback執(zhí)行流程

requestIdleCallback執(zhí)行流程

Fiber是什么

Fiber是一個執(zhí)行單元

Fiber 是一個執(zhí)行單元,每次執(zhí)行完一個執(zhí)行單元, React 就會檢查現(xiàn)在還剩多少時間,如果沒有時間就將控制權(quán)讓出去

Fiber是一個執(zhí)行單元

Fiber是一種數(shù)據(jù)結(jié)構(gòu)

React 目前的做法是使用鏈表, 每個 VirtualDOM 節(jié)點內(nèi)部表示為一個Fiber,它可以用一個 JS 對象來表示:

const fiber = {
  stateNode, // 節(jié)點實例
  child,     // 子節(jié)點
  sibling,   // 兄弟節(jié)點
  return,    // 父節(jié)點
}

Fiber是一種數(shù)據(jù)結(jié)構(gòu)

Fiber之前的協(xié)調(diào)階段

  • React 會遞歸比對VirtualDOM樹,找出需要變動的節(jié)點,然后同步更新它們。這個過程 React 稱為Reconcilation(協(xié)調(diào))
  • 在Reconcilation期間,React 會一直占用著瀏覽器資源,一則會導(dǎo)致用戶觸發(fā)的事件得不到響應(yīng), 二則會導(dǎo)致掉幀,用戶可能會感覺到卡頓

let root = {
  key: 'A1',
  children: [
    {
      key: 'B1',
      children: [
        {
          key: 'C1',
          children: []
        },
        {
          key: 'C2',
          children: []
        }
      ]
    },
    {
      key: 'B2',
      children: []
    }
  ]
}


function walk(element) {
  doWork(element);
  element.children.forEach(walk);
}


function doWork(element) {
  console.log(element.key);
}
walk(root);

在 Fiber 出現(xiàn)之前, React 會不斷遞歸遍歷虛擬 DOM 節(jié)點,占用著瀏覽器資源,積極地浪費性能,造成卡頓現(xiàn)象,且協(xié)調(diào)階段是不能被打斷的。

React不斷遞歸遍歷虛擬DOM節(jié)點

Fiber 出現(xiàn)之后,通過某些 Fiber 調(diào)度策略合理分配 CPU 資源,讓自己的協(xié)調(diào)階段變成可被終端,適時地讓 CPU(瀏覽器)執(zhí)行權(quán),提高了性能優(yōu)化。

協(xié)調(diào)階段變成可被終端

Fiber執(zhí)行階段

每次渲染有兩個階段:Reconciliation(協(xié)調(diào)\render階段)和Commit(提交階段)

  • 協(xié)調(diào)階段: 這個階段可以被中斷, 通過Dom-Diff算法找出所有節(jié)點變更,例如節(jié)點新增刪除、屬性變更等等, 這些變更React 稱之為副作用(Effect)
  • 提交階段: 將上一個階段計算出來的需要處理的副作用(Effects)一次性執(zhí)行了。這個階段必須同步執(zhí)行,不能被打斷

簡單理解的話

  • 階段1:生成Fiber樹,得出需要更新節(jié)點信息。(可打斷
  • 階段2:將需要更新的節(jié)點一次性地批量更新。(不可打斷

Fiber的協(xié)調(diào)階段,可以被優(yōu)先級較高的任務(wù)(如鍵盤輸入)打斷。

階段1可被打斷的特性,讓優(yōu)先級更高的任務(wù)先執(zhí)行,從框架層面大大降低了頁面掉幀的概率。

Fiber執(zhí)行流程

render階段

Fiber Reconciliation(協(xié)調(diào)) 在階段一進(jìn)行 Diff 計算的時候,會生成一棵 Fiber 樹。這棵樹是在 Virtual DOM 樹的基礎(chǔ)上增加額外的信息生成來的,它本質(zhì)來說是一個鏈表。

render階段

commit提交階段

Fiber 樹在首次渲染的時候會一次過生成。在后續(xù)需要 Diff 的時候,會根據(jù)已有樹和最新 Virtual DOM 的信息,生成一棵新的樹。這顆新樹每生成一個新的節(jié)點,都會將控制權(quán)交回給主線程,去檢查有沒有優(yōu)先級更高的任務(wù)需要執(zhí)行。如果沒有,則繼續(xù)構(gòu)建樹的過程。

1.如果過程中有優(yōu)先級更高的任務(wù)需要進(jìn)行,則 Fiber Reconciler 會丟棄正在生成的樹,在空閑的時候再重新執(zhí)行一遍。

2.在構(gòu)造 Fiber 樹的過程中,F(xiàn)iber Reconciler 會將需要更新的節(jié)點信息保存在Effect List當(dāng)中,在階段二執(zhí)行的時候,會批量更新相應(yīng)的節(jié)點。

細(xì)節(jié)拓展

render階段是如何遍歷,生成Fiber樹的?

<div>

  

    

      

      

    

    

  
</div>

  • 從頂點開始遍歷
  • 如果有第一個兒子,先遍歷第一個兒子
  • 如果沒有第一個兒子,標(biāo)志著此節(jié)點遍歷完成
  • 如果有弟弟遍歷弟弟
  • 如果有沒有下一個弟弟,返回父節(jié)點標(biāo)識完成父節(jié)點遍歷,如果有叔叔遍歷叔叔
  • 沒有父節(jié)點遍歷結(jié)束

render節(jié)點遍歷規(guī)則

commit階段,是如何commit的?

類比 Git 分支功能,從舊樹中 fork 出來一份,在新分支進(jìn)行添加、刪除和更新操作,經(jīng)過測試后進(jìn)行提交。

commit階段,是如何commit的

以上就是W3Cschool編程獅關(guān)于你也可以理解的React Fiber,學(xué)廢了嗎的相關(guān)介紹了,希望對大家有所幫助。

0 人點贊