App下載

你也可以理解的React Fiber,學廢了嗎

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

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

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

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

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

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

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

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

屏幕刷新率

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

Fiber的誕生

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

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

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

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

requestIdleCallback的核心用法

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

requestIdleCallback執(zhí)行流程

requestIdleCallback執(zhí)行流程

Fiber是什么

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

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

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

Fiber是一種數(shù)據(jù)結構

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

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

Fiber是一種數(shù)據(jù)結構

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

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

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í)行權,提高了性能優(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)先級較高的任務(如鍵盤輸入)打斷。

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

Fiber執(zhí)行流程

render階段

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

render階段

commit提交階段

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

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

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

細節(jié)拓展

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

<div>

  

    

      

      

    

    

  
</div>

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

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

commit階段,是如何commit的?

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

commit階段,是如何commit的

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

0 人點贊