React Fiber

隨著 React 16 的發(fā)布,Hooks 的正式上線,很多小伙伴都很興奮,都想要嘗試這一新的特性,升級(jí) React 的意愿越來(lái)越強(qiáng)烈了。

我們都知道 React 是一個(gè)優(yōu)秀的前端框架,很多的大型應(yīng)用都在使用,而作為使用 React 為工具的開(kāi)發(fā)者也應(yīng)該了解下 React Fiber,F(xiàn)iber 到底是什么?它能給我們帶來(lái)什么?以及 React 團(tuán)隊(duì)為什么要去重寫(xiě) Fiber 架構(gòu)?

什么是 React Fiber ?

React Fiber 是 React 16 中新的協(xié)調(diào)引擎,是對(duì)核心算法的一次重新實(shí)現(xiàn)。

既然是新的,那 React 團(tuán)隊(duì)為什么要重寫(xiě)新的 Fiber 架構(gòu)呢?

在 React 16 以前,當(dāng)元素較多,需要頻繁刷新的時(shí)候頁(yè)面會(huì)出現(xiàn)卡頓,究其原因是因?yàn)楦逻^(guò)程是同步的,大量的同步計(jì)算任務(wù)阻塞了瀏覽器的渲染。

當(dāng)頁(yè)面加載或者更新時(shí),React 會(huì)去計(jì)算和比對(duì) Virtual DOM,最后繪制頁(yè)面,整個(gè)過(guò)程是同步進(jìn)行的。當(dāng) JavaScript 在瀏覽器的主線程上長(zhǎng)期運(yùn)行,就會(huì)阻塞了樣式計(jì)算、布局和繪制,導(dǎo)致頁(yè)面無(wú)法得到及時(shí)的更新和響應(yīng)。此時(shí),無(wú)論用戶如何點(diǎn)擊鼠標(biāo)或者敲擊鍵盤(pán)都不會(huì)得到響應(yīng),當(dāng) React 更新完成后剛剛點(diǎn)擊或敲擊的事件才會(huì)得到響應(yīng)。

由于 JavaScript 是單線程的特點(diǎn),所以一個(gè)線程執(zhí)行完成后才會(huì)執(zhí)行下一個(gè)線程,當(dāng)上一個(gè)線程任務(wù)耗時(shí)太長(zhǎng),程序就會(huì)對(duì)其他輸入不作出響應(yīng)。

React 的更新過(guò)程會(huì)先計(jì)算,一旦任務(wù)開(kāi)始進(jìn)行,就無(wú)法中斷, js 將一直占用主線程, 直到整棵 Virtual DOM 樹(shù)計(jì)算完成之后,才能把執(zhí)行權(quán)交給渲染引擎,而 React Fiber 就是要改變現(xiàn)狀。

React 16 以前的更新過(guò)程
它能給我們帶來(lái)什么?
  • 增量渲染

  • 為不同任務(wù)分配優(yōu)先極

  • 更新時(shí)能暫停、終止、復(fù)用渲染任務(wù)

  • 并發(fā)方面新的能力

接下來(lái),讓我們具體來(lái)了解下 Fiber。

Fiber

Fiber 把耗時(shí)長(zhǎng)的任務(wù)拆分成很多的小片,每個(gè)小片的運(yùn)行時(shí)間很短,每次只執(zhí)行一個(gè)小片,執(zhí)行完后看是否還有剩余時(shí)間,如果有就繼續(xù)執(zhí)行下個(gè)小片,如果沒(méi)有就掛起當(dāng)前任務(wù),將控制權(quán)交給 React 負(fù)責(zé)任務(wù)協(xié)調(diào)的模塊,看有沒(méi)有其他緊急任務(wù)要做,如果沒(méi)有就繼續(xù)更新當(dāng)前任務(wù),如果有緊急任務(wù)就去做緊急任務(wù),等主線程不忙的時(shí)候在繼續(xù)執(zhí)行當(dāng)前任務(wù)。

分片之后,每執(zhí)行一段時(shí)間,都會(huì)將控制權(quán)交給主線程。

這樣唯一的線程不會(huì)被獨(dú)占,其他任務(wù)依然有運(yùn)行的機(jī)會(huì)。

這種策略叫做 Cooperative Scheduling(合作式調(diào)度),操作系統(tǒng)常用任務(wù)調(diào)度策略之一。

總而言之,我們了解到,F(xiàn)iber 是一個(gè)最小工作單元,也是堆棧的重新實(shí)現(xiàn),可以理解為是一個(gè)虛擬的堆棧幀。它將可中斷的任務(wù)拆分成多個(gè)任務(wù),通過(guò)優(yōu)先級(jí)來(lái)自由調(diào)度子任務(wù),分段更新,從而將之前的同步渲染改為異步渲染。

Fiber 結(jié)構(gòu)

維護(hù)每一個(gè)分片的數(shù)據(jù)結(jié)構(gòu),就是 Fiber,它可以用 JS 對(duì)象來(lái)表示,其中包含有關(guān)組件,以及輸入和輸出的信息:

const fiber = {
  // 跟當(dāng)前 Fiber 相關(guān)本地狀態(tài)(比如瀏覽器環(huán)境就是 DOM 節(jié)點(diǎn))
  stateNode: any, // 節(jié)點(diǎn)實(shí)例
  // 指向他在 Fiber 節(jié)點(diǎn)樹(shù)中的 `parent`,用來(lái)在處理完這個(gè)節(jié)點(diǎn)之后向上返回
  return: Fiber | null, // 父節(jié)點(diǎn)
  // 單鏈表樹(shù)結(jié)構(gòu)
  child: Fiber | null, // 指向自己的第一個(gè)子節(jié)點(diǎn)
  sibling: Fiber | null, // 指向自己的兄弟結(jié)構(gòu),兄弟節(jié)點(diǎn)的return指向同一個(gè)父節(jié)點(diǎn)
  index: number,

  // 組件相關(guān)
  tag: WorkTag, // 標(biāo)記不同的組件類型
  key: null | string, // ReactElement 里面的 key,與 type 一起,主要用來(lái)在reconciliation 期間確定 Fiber 是否可重用。
  elementType: any, // ReactElement.type,也就是我們調(diào)用 `createElement` 的第一個(gè)參數(shù)
  type: any,  // 對(duì)于復(fù)合組件,type 是函數(shù)或者是類組件(`function`或者`class`),對(duì)于標(biāo)準(zhǔn)組件(div或者span),type 是 string
    
  // ref屬性
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

  // 更新相關(guān)
  // 當(dāng)傳入的 pendingProps 和 memoizedProps 相同的時(shí)候,表示 fiber 可以重新使用之前的 fiber,以避免重復(fù)的工作。
  pendingProps: any,  // 新的變動(dòng)帶來(lái)的新的props
  memoizedProps: any,  // 上一次渲染完成之后的props
  updateQueue: UpdateQueue<any> | null,  // 該Fiber對(duì)應(yīng)的組件產(chǎn)生的Update會(huì)存放在這個(gè)隊(duì)列里面
  memoizedState: any, // 上一次渲染的時(shí)候的state
  firstContextDependency: ContextDependency<mixed> | null, // 一個(gè)列表,存放這個(gè)Fiber依賴的context
  pendingWorkPriority: number, // 待處理的工作優(yōu)先級(jí),除 NoWork 為0外,數(shù)字越大表示優(yōu)先級(jí)越低。
    
  // Scheduler 相關(guān)
  expirationTime: ExpirationTime,  // 代表任務(wù)在未來(lái)的哪個(gè)時(shí)間點(diǎn)應(yīng)該被完成,不包括他的子樹(shù)產(chǎn)生的任務(wù)
  // 快速確定子樹(shù)中是否有不在等待的變化
  childExpirationTime: ExpirationTime,

  // 用來(lái)描述當(dāng)前Fiber和他子樹(shù)的`Bitfield`
  // 共存的模式表示這個(gè)子樹(shù)是否默認(rèn)是異步渲染的
  // Fiber被創(chuàng)建的時(shí)候他會(huì)繼承父Fiber
  // 其他的標(biāo)識(shí)也可以在創(chuàng)建的時(shí)候被設(shè)置
  // 但是在創(chuàng)建之后不應(yīng)該再被修改,特別是他的子Fiber創(chuàng)建之前
  mode: TypeOfMode,
    
  // 在Fiber樹(shù)更新的過(guò)程中,每個(gè)Fiber都會(huì)有一個(gè)跟其對(duì)應(yīng)的Fiber
  // 我們稱他為`current <==> workInProgress`
  // 在渲染完成之后他們會(huì)交換位置
  alternate: Fiber | null,

  // Effect 相關(guān)的
  effectTag: SideEffectTag, // 用來(lái)記錄Side Effect
  nextEffect: Fiber | null, // 單鏈表用來(lái)快速查找下一個(gè)side effect
  firstEffect: Fiber | null,  // 子樹(shù)中第一個(gè)side effect
  lastEffect: Fiber | null, // 子樹(shù)中最后一個(gè)side effect
}
任務(wù)的優(yōu)先級(jí)

上面講到了任務(wù)的執(zhí)行是根據(jù)優(yōu)先級(jí)來(lái)調(diào)度的,那我們現(xiàn)在具體了解一下優(yōu)先級(jí)。

  • synchronous 同步執(zhí)行,首屏使用

  • task 在 next tick 之前執(zhí)行

  • animation 下一幀之前執(zhí)行,通過(guò)requestAnimationFrame來(lái)調(diào)度,這樣在下一幀就能立即開(kāi)始動(dòng)畫(huà)過(guò)程

  • high 在不久的將來(lái)立即執(zhí)行

  • low 稍微延遲(100-200ms)執(zhí)行也沒(méi)關(guān)系

  • offscreen 當(dāng)前隱藏的、屏幕外的(看不見(jiàn)的)元素,在下一次 render 時(shí)或 scroll 時(shí)才執(zhí)行

Fiber reconciler

Fiber Reconciler 決定了當(dāng)任務(wù)調(diào)度完成之后,如何去執(zhí)行每個(gè)任務(wù),如何去更新每一個(gè)節(jié)點(diǎn)的過(guò)程。

React Fiber 更新過(guò)程分為兩個(gè)階段

  • 第一階段 Reconciliation Phase,生成 Fiber 樹(shù),得出需要更新的節(jié)點(diǎn)信息。是一個(gè)漸進(jìn)的過(guò)程,可以被打斷。可能會(huì)調(diào)用 componentWillMount,componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate 生命周期函數(shù)。

  • 第二階段 Commit Phase,將需要更新的節(jié)點(diǎn)批量更新,這個(gè)過(guò)程不能被打斷??赡軙?huì)調(diào)用 componentDidMount,componentDidUpdate,componentWillUnmount 生命周期函數(shù)。

Fiber 更新過(guò)程
Fiber tree 與 WorkInProgress Tree
// 單鏈表樹(shù)結(jié)構(gòu)
{
   return: Fiber | null, // 指向父節(jié)點(diǎn)
   child: Fiber | null, // 指向自己的第一個(gè)子節(jié)點(diǎn)
   sibling: Fiber | null, // 指向自己的兄弟結(jié)構(gòu),兄弟節(jié)點(diǎn)的 return 指向同一個(gè)父節(jié)點(diǎn)
}

首次渲染之后 React 會(huì)得到一個(gè) Fiber 樹(shù),也就是 Current tree(當(dāng)前樹(shù))。當(dāng)處理更新的時(shí)候,React 會(huì)構(gòu)建 WorkInProgress Tree(工作過(guò)程樹(shù)),當(dāng)構(gòu)造完成后會(huì)將 current 指針指向 WorkInProgress Tree,WorkInProgress Tree 成了新的 Fiber tree。

這被稱做雙緩沖。以 Fiber tree 為主,WorkInProgress Tree 為輔。

雙緩沖技術(shù)可以復(fù)用內(nèi)部對(duì)象(fiber),節(jié)省內(nèi)存分配、GC的時(shí)間開(kāi)銷。

總結(jié)

本篇文章篇幅有限,大致講了 Fiber 的相關(guān)知識(shí)和 React 的工作流程,對(duì)于細(xì)節(jié),比如:如何調(diào)度任務(wù),如何 diff 等,感興趣的同學(xué)可以自行結(jié)合源碼研究分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文鏈接https://blog.csdn.net/weixin_34253126/article/details...
    雷霆克呂齊閱讀 318評(píng)論 0 1
  • 前言 React Fiber 不是一個(gè)新的東西,但在前端領(lǐng)域是第一次廣為認(rèn)知的應(yīng)用。幾年前全新的Fiber架構(gòu)讓剛...
    這個(gè)前端不太冷閱讀 5,719評(píng)論 2 8
  • > 本文重點(diǎn):介紹React重構(gòu)的起因和目的,理解Fiber tree單向鏈表結(jié)構(gòu)中各屬性含義,梳理調(diào)度過(guò)程和核心...
    intopiece_檳閱讀 1,498評(píng)論 0 0
  • 前言 隨著mvvm模式的流行,現(xiàn)在大多數(shù)的前端框架基本都是在react和vue中選擇其一,react的核心就是盡量...
    鄒小鄒大廚閱讀 570評(píng)論 0 1
  • 上次寫(xiě)了react整體框架的理解,這次想寫(xiě)寫(xiě)看對(duì)于新版React的新的React Fiber的實(shí)現(xiàn)。 在React...
    離開(kāi)North閱讀 1,723評(píng)論 1 2

友情鏈接更多精彩內(nèi)容