自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

「從0實(shí)現(xiàn)React18系列」Fiber架構(gòu)的實(shí)現(xiàn)原理

開發(fā) 架構(gòu)
在本節(jié)我們對(duì)Reconciler的架構(gòu)有了大概的認(rèn)知,了解了傳統(tǒng)的庫(kù)與現(xiàn)代框架的工作原理,也掌握了預(yù)編譯和即時(shí)編譯的區(qū)別,以及它們?cè)诂F(xiàn)代框架中的應(yīng)用。

Reconciler 是什么

Reconciler 是 React 核心邏輯所在的模塊,中文名叫協(xié)調(diào)器。

Reconciler 架構(gòu)介紹

在React中,Reconciler?(協(xié)調(diào)器)是負(fù)責(zé)管理虛擬DOM樹更新的關(guān)鍵部分。當(dāng)組件狀態(tài)或?qū)傩园l(fā)生更改時(shí),Reconciler的任務(wù)是確定如何有效地更新DOM來(lái)反映這些更改。這個(gè)過(guò)程通常被稱為 "協(xié)調(diào)"(Reconciliation)。

Reconciler?的核心思想是通過(guò)將新的虛擬DOM樹與舊的虛擬DOM樹進(jìn)行比較,找出需要實(shí)際更新的部分,然后最小化實(shí)際DOM操作的數(shù)量。這個(gè)過(guò)程被稱為"diffing"算法。

傳統(tǒng)庫(kù)與現(xiàn)代框架的工作原理

在傳統(tǒng)的庫(kù)(jQuery)工作原理(過(guò)程驅(qū)動(dòng))。

圖片

在傳統(tǒng)的前端開發(fā)中使用的jQuery?庫(kù)的工作原理主要是通過(guò)一個(gè)簡(jiǎn)化和統(tǒng)一的API,使得開發(fā)者能夠更容易地操作DOM、處理事件、創(chuàng)建動(dòng)畫以及發(fā)起AJAX請(qǐng)求等。而不是描述UI的狀態(tài)。所以jQuery的工作原理是過(guò)程驅(qū)動(dòng)的。

現(xiàn)代的前端框架結(jié)構(gòu)與工作原理(狀態(tài)驅(qū)動(dòng))。

圖片

現(xiàn)代的前端框架結(jié)構(gòu)與工作原理

  • 在現(xiàn)代的前端框架中,開發(fā)者使用描述UI的方法(template或JSX)來(lái)定義組件和它們之間的關(guān)系。
  • 運(yùn)行時(shí)核心模塊根據(jù)描述的UI,管理組件的創(chuàng)建、更新和銷毀,處理數(shù)據(jù)狀態(tài)變更,并通過(guò)虛擬DOM或響應(yīng)式系統(tǒng)來(lái)優(yōu)化UI更新性能。
  • 當(dāng)需要與宿主環(huán)境交互時(shí)(如操作DOM、處理事件或發(fā)起網(wǎng)絡(luò)請(qǐng)求),運(yùn)行時(shí)核心模塊會(huì)調(diào)用宿主環(huán)境API。前端框架通常會(huì)封裝這些API,提供統(tǒng)一的跨平臺(tái)接口。

AOT預(yù)編譯 與 JIT即時(shí)編譯

現(xiàn)代框架都需要“編譯”這一步驟,用于:

  • 將“框架中描述的UI”轉(zhuǎn)換為宿主環(huán)境可識(shí)別的代碼。
  • 代碼轉(zhuǎn)化,比如將ts編譯成js,實(shí)現(xiàn)polyfill等。
  • 執(zhí)行一些編譯時(shí)優(yōu)化。

“編譯”可以選擇兩個(gè)時(shí)機(jī)執(zhí)行:

  • 代碼在構(gòu)建時(shí),被稱為 AOT(Ahead Of Time,提前編譯或預(yù)編譯),宿主環(huán)境獲得是編譯后的代碼。
  • 代碼在宿主環(huán)境執(zhí)行時(shí),被稱為 JIT(Just In Time,即時(shí)編譯),代碼在宿主環(huán)境中編譯并執(zhí)行。

大部分采用模板語(yǔ)法描述UI的前端框架都會(huì)進(jìn)行AOT優(yōu)化,例如:Vue3、Angular、Svelte。

其本質(zhì)原因在于模板語(yǔ)法時(shí)固定的,固定意味著“可分析”,“可分析”意味著在編譯時(shí)可以標(biāo)記模板語(yǔ)法中的靜態(tài)部分(不變的部分)與動(dòng)態(tài)部分(包含自變量、可變的部分)。

但采用JSX語(yǔ)法描述UI的前端框架很難從 AOT中受益,因?yàn)镴SX是ES的語(yǔ)法糖,ES語(yǔ)句的靈活性使其很難進(jìn)行靜態(tài)分析。

拓展 那么Template語(yǔ)法是如何從中受益的呢?

  1. 解析:將模板字符串解析成抽象語(yǔ)法樹(AST)。AST是一種樹形結(jié)構(gòu),用于表示模板中的元素、屬性、文本節(jié)點(diǎn)等。
  2. 優(yōu)化:遍歷AST,對(duì)其中的靜態(tài)內(nèi)容(如純文本節(jié)點(diǎn)、靜態(tài)屬性等)進(jìn)行標(biāo)記。這些標(biāo)記在后續(xù)的渲染過(guò)程中有助于避免不必要的計(jì)算和更新,從而提高性能。
  3. 代碼生成:將優(yōu)化后的AST轉(zhuǎn)換成可執(zhí)行的JavaScript代碼。這通常包括生成渲染函數(shù)(render function)和虛擬DOM節(jié)點(diǎn)。渲染函數(shù)用于創(chuàng)建和更新實(shí)際的DOM結(jié)構(gòu)。

模板語(yǔ)法由于在構(gòu)建時(shí)已經(jīng)被編譯成可執(zhí)行的JavaScript代碼,運(yùn)行時(shí)無(wú)需再進(jìn)行解析和編譯,從而減少了性能開銷。

ReactElement 數(shù)據(jù)結(jié)構(gòu)的不足

回歸主題,根據(jù)前面的學(xué)習(xí),我們知道了 JSX 方法執(zhí)行后會(huì)返回一個(gè)新的 React 元素(ReactElement)。React 元素是一個(gè)輕量級(jí)的對(duì)象,描述了要渲染的 UI 組件的類型(type)、屬性(props),和子元素(children)等信息。

這里可以給自己個(gè)問(wèn)題,如果ReactElement?作為reconciler核心模塊操作的數(shù)據(jù)結(jié)構(gòu),會(huì)存在哪些問(wèn)題:

  • 無(wú)法表達(dá)ReactElement節(jié)點(diǎn)與另一個(gè)ReactElement節(jié)點(diǎn)之間的關(guān)系(因?yàn)樗挥涗浟俗陨淼臄?shù)據(jù),比如組件的類型、屬性和子元素等),一般把ReactElement稱為React的數(shù)據(jù)存儲(chǔ)單元。
  • 字段有限,不好拓展(比如:無(wú)法表達(dá)狀態(tài))。

從下圖中可以看到,ReactELement這種數(shù)據(jù)結(jié)構(gòu)很有限,在節(jié)點(diǎn)屬性關(guān)聯(lián)方面也只有 children,并沒(méi)有保存兄弟節(jié)點(diǎn)以及父節(jié)點(diǎn)之間的關(guān)系:

圖片

當(dāng)然在React 16版本之前,React 使用的是名為Stack Reconciler的舊調(diào)和算法。Stack Reconciler 的核心是遞歸遍歷組件樹,把數(shù)據(jù)保存在遞歸調(diào)用棧中。它使用的深層遞歸遍歷方法。

但是使用遞歸遍歷組件樹時(shí),會(huì)導(dǎo)致一些問(wèn)題:

  • 阻塞主線程:在 JavaScript 中,遞歸調(diào)用可能會(huì)阻塞主線程,因?yàn)?JavaScript 是單線程的。如果組件樹很大或者更新很頻繁,遞歸調(diào)用可能會(huì)導(dǎo)致 UI 變得不流暢,影響用戶體驗(yàn)。
  • 沒(méi)有優(yōu)先級(jí)調(diào)度:Stack Reconciler 無(wú)法對(duì)不同的更新任務(wù)進(jìn)行優(yōu)先級(jí)調(diào)度,所有的更新任務(wù)都會(huì)被視為相同的優(yōu)先級(jí)。這意味著對(duì)于高優(yōu)先級(jí)的任務(wù)(如動(dòng)畫或用戶交互),React 無(wú)法優(yōu)先處理,從而可能導(dǎo)致性能下降。

為了解決這些問(wèn)題,React 引入了 Fiber Reconciler?。Fiber Reconciler? 使用了一種名為 "Fiber" 的新數(shù)據(jù)結(jié)構(gòu)來(lái)表示組件樹。

它的特點(diǎn):

  • 介于ReactElement與真實(shí)UI節(jié)點(diǎn)之間。
  • 能夠表達(dá)節(jié)點(diǎn)之間的關(guān)系。
  • 方便拓展,不僅作為數(shù)據(jù)存儲(chǔ)單元,也能作為工作單元。

FiberNode 是虛擬DOM在React中的實(shí)現(xiàn)。

FiberNode Tree的數(shù)據(jù)結(jié)構(gòu)如圖所示:

圖片

FiberNode 上有很多屬性,包括和自身相關(guān)的屬性 ref,節(jié)點(diǎn)之間的關(guān)系 return、silbing還有工作單元上的屬性,比如 pendingProps等等,后面會(huì)詳細(xì)介紹。

Fiber出現(xiàn)的意義

Fiber最主要的兩層含義:

  1. 作為靜態(tài)的數(shù)據(jù)結(jié)構(gòu)來(lái)說(shuō),每個(gè)Fiber節(jié)點(diǎn)對(duì)應(yīng)一個(gè)React element,保存了該組件的類型(函數(shù)組件/類組件/原生組件...)、對(duì)應(yīng)的DOM節(jié)點(diǎn)等信息。
  2. 作為動(dòng)態(tài)的工作單元來(lái)說(shuō),每個(gè)Fiber節(jié)點(diǎn)保存了本次更新中該組件改變的狀態(tài)、要執(zhí)行的工作(需要被刪除/被插入頁(yè)面中/被更新...)。

Fiber的出現(xiàn)也為React帶來(lái)了很多意義:

從優(yōu)化層面來(lái)說(shuō),F(xiàn)iber是一種新的調(diào)和算法(reconciliation algorithm)。

  1. 增量渲染:在早期的 React 版本(Stack Reconciler)中,當(dāng)有組件更新時(shí),React 會(huì)一次性完成整個(gè)組件樹的調(diào)和過(guò)程。這會(huì)導(dǎo)致長(zhǎng)時(shí)間的 JavaScript 執(zhí)行阻塞,從而影響用戶界面的響應(yīng)性。Fiber 引入了增量渲染的概念,允許將調(diào)和過(guò)程分成多個(gè)小任務(wù),這些任務(wù)可以在瀏覽器的空閑時(shí)間內(nèi)執(zhí)行。這樣,即使在復(fù)雜的應(yīng)用程序中,React 也能實(shí)現(xiàn)更平滑的用戶界面更新。
  2. 任務(wù)調(diào)度:Fiber 引入了任務(wù)優(yōu)先級(jí)的概念,使得 React 可以根據(jù)任務(wù)的優(yōu)先級(jí)來(lái)調(diào)度它們的執(zhí)行。這意味著較高優(yōu)先級(jí)的任務(wù)(如用戶交互事件)可以打斷較低優(yōu)先級(jí)的任務(wù)(如數(shù)據(jù)加載),從而實(shí)現(xiàn)更靈活的任務(wù)調(diào)度。這有助于提高應(yīng)用程序的響應(yīng)性和性能。

這兩個(gè)概念會(huì)在后面的章節(jié)詳細(xì)講解。

Fiber是什么?

Fiber是React的最小的工作單元。在React的世界中,一切都可以是組件。在普通的HTML頁(yè)面上,開發(fā)者們可以將多個(gè)DOM元素整合在一起組成一個(gè)組件。

普通的DOM元素(HostComponent)可以是組件,普通的文本節(jié)點(diǎn)(HostText)也可以是組件。還有通過(guò)ReactDom.render方法創(chuàng)建的根元素(RootElement)也可以是組件,還有經(jīng)常在React中使用的函數(shù)組件(FunctionComponent)。

在React源碼中,每個(gè)FiberNode都有一個(gè)WorkTag屬性,用于標(biāo)識(shí)當(dāng)前節(jié)點(diǎn)的類型。

// pagkages/react-reconciler/src/ReactWorkTags.ts
export type WorkTag =
| typeof FunctionComponent
| typeof ClassComponent
| typeof HostRoot
| typeof HostComponent
| typeof HostText
| typeof Fragment;

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostRoot = 3; // 通過(guò)ReactDom.render()產(chǎn)生的根元素

export const HostComponent = 5; // dom元素 比如 <div></div>
export const HostText = 6; // 文本類型 比如:<div>123</div>
export const Fragment = 7; // <Fragment />

ReactWorkTags.ts文件中定義了所有可能的節(jié)點(diǎn)類型,每個(gè)類型都應(yīng)一個(gè)number類型的值。

這樣做的好處是可以通過(guò)比較兩個(gè)節(jié)點(diǎn)的 WorkTag 屬性來(lái)判斷它們是否是同一類型的節(jié)點(diǎn),而不需要通過(guò)字符串比較等方式,這樣可以提高比較的效率,也可以減少出錯(cuò)的可能性。

每一個(gè)組件都對(duì)應(yīng)著一個(gè)FiberNode?,許多個(gè)FiberNode?互相嵌套、關(guān)聯(lián)就組成了FiberNode Tree?。正如下面表示的FiberNode Tree和DOM樹的關(guān)系一樣:

Fiber樹                    DOM樹

div#root div#root
| |
<App/> div
| / \
div p a
/
/
p ----> <Child/>
|
a

一個(gè)DOM節(jié)點(diǎn)一定對(duì)應(yīng)著一個(gè)FiberNode,但每一個(gè)Fiber節(jié)點(diǎn)缺不一定有對(duì)應(yīng)的DOM節(jié)點(diǎn)。

因?yàn)镽eact支持不同類型的組件,因此每個(gè)FiberNode并不一定具有對(duì)應(yīng)的DOM節(jié)點(diǎn)。

  • 函數(shù)組件:函數(shù)組件是一個(gè)簡(jiǎn)單的函數(shù),它接收屬性(props)并返回JSX。這個(gè)JSX可能包含DOM節(jié)點(diǎn),但這個(gè)DOM節(jié)點(diǎn)并不是真實(shí)的DOM節(jié)點(diǎn),而是當(dāng)React渲染組件時(shí),它會(huì)將函數(shù)組件返回的JSX轉(zhuǎn)換成真實(shí)的DOM節(jié)點(diǎn)。所以函數(shù)組件本身并不會(huì)直接映射到一個(gè)DOM節(jié)點(diǎn)。
  • Fragment:React Fragment是一種特殊的組件,用于在不添加額外DOM節(jié)點(diǎn)的情況下返回多個(gè)子元素。當(dāng)遍歷組件樹時(shí),React會(huì)將Fragment的子元素視為直接子元素,而不會(huì)為Fragment本身創(chuàng)建DOM節(jié)點(diǎn)。
  • 還有很多,不一一舉例了。

Fiber工作單元的結(jié)構(gòu)

Fiber作為工作單元,它有很多屬性:

  • Fiber實(shí)例屬性: tag、key、type、stateNode等。
  • 與其它節(jié)點(diǎn)關(guān)系的鏈表屬性:return、child、sibling、index。
  • Ref相關(guān)的屬性:ref。
  • Fiber更新相關(guān)的屬性:pendingProps、memoizedProps、memoizedState、updateQueue、alternate。
  • Fiber Effect:flags、subtreeFlags、deletions。
// pagkages/react-reconciler/src/ReactFiber.js
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
) {
// Instance
this.tag = tag;
this.key = key;
this.type = null; // fiber對(duì)應(yīng)的DOM元素的標(biāo)簽類型,div、p...
this.stateNode = null; // fiber的實(shí)例,類組件場(chǎng)景下,是組件的類,HostComponent場(chǎng)景,是dom元素

this.ref = null; // ref相關(guān)

// Fiber 除了有自身實(shí)例上的屬性,還需要有表示和其它節(jié)點(diǎn)的關(guān)系
this.return = null; // 指向父級(jí)fiber
this.child = null; // 指向子fiber
this.sibling = null; // 同級(jí)兄弟fiber
this.index = 0;

// 作為工作單元與Fiber更新相關(guān)
this.pendingProps = pendingProps; // 剛開始工作階段的 props
this.memoizedProps = null; // 工作結(jié)束時(shí)確定下來(lái)的 props
this.memoizedState = null; // 更新完成后的新 state
this.updateQueue = null; // Fiber產(chǎn)生的更新操作都會(huì)放在更新隊(duì)列中

// Effects
this.flags = NoFlags; // 比如插入 更改 刪除dom等)初始狀態(tài)時(shí)表示沒(méi)有任何標(biāo)記(因?yàn)檫€沒(méi)進(jìn)行fiberNode對(duì)比)
this.subtreeFlags = NoFlags; // 子節(jié)點(diǎn)副作用標(biāo)識(shí)
this.deletions = null; // 用于存放被刪除的子節(jié)點(diǎn)

/*
* 可以看成是workInProgress(或current)樹中的和它一樣的節(jié)點(diǎn),
* 可以通過(guò)這個(gè)字段是否為null判斷當(dāng)前這個(gè)fiber處在更新還是創(chuàng)建過(guò)程
* */
this.alternate = null; // 用于 current Fiber樹和 workInProgress Fiber樹的切換(如果當(dāng)時(shí)fiberNode樹是current樹,則alternate指向的是workInProgress樹)
}

這里Fiber節(jié)點(diǎn)的屬性沒(méi)有寫完全,可以去react源碼里看,地址在代碼塊首行。

雖然屬性很多,但可以按三層含義將它們分類來(lái)看:

作為架構(gòu)來(lái)說(shuō)

每個(gè)Fiber節(jié)點(diǎn)有個(gè)對(duì)應(yīng)的React element,多個(gè)Fiber節(jié)點(diǎn)是如何連接形成樹呢?靠如下三個(gè)屬性:

// 指向父級(jí)Fiber節(jié)點(diǎn)
this.return = null;
// 指向子Fiber節(jié)點(diǎn)
this.child = null;
// 指向右邊第一個(gè)兄弟Fiber節(jié)點(diǎn)
this.sibling = null;

舉個(gè)例子,比如下面的組件結(jié)構(gòu):

function App() {
return (
<div>
i am
<span>時(shí)光屋小豪</span>
<span>fighting</span>
</div>
)
}

對(duì)應(yīng)的FiberNode Tree結(jié)構(gòu):

圖片

作為靜態(tài)的數(shù)據(jù)結(jié)構(gòu)

作為靜態(tài)的數(shù)據(jù)結(jié)構(gòu),需要保存組件的相關(guān)的信息:

// Fiber對(duì)應(yīng)組件的類型 Function/Class/Host...
this.tag = tag;
// key屬性
this.key = key;
// 大部分情況同type,某些情況不同,比如FunctionComponent使用React.memo包裹
this.elementType = null;
// 對(duì)于 FunctionComponent,指函數(shù)本身,對(duì)于ClassComponent,指class,對(duì)于HostComponent,指DOM節(jié)點(diǎn)tagName
this.type = null;
// Fiber對(duì)應(yīng)的真實(shí)DOM節(jié)點(diǎn)
this.stateNode = null;

作為動(dòng)態(tài)的工作單元

作為動(dòng)態(tài)的工作單元,F(xiàn)iber中如下參數(shù)保存了本次更新相關(guān)的信息,會(huì)在后續(xù)的更新流程章節(jié)中使用到具體屬性時(shí)再詳細(xì)介紹。

// 保存本次更新造成的狀態(tài)改變相關(guān)信息
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

// 保存本次更新會(huì)造成的DOM操作
this.effectTag = NoEffect;
this.nextEffect = null;

this.firstEffect = null;
this.lastEffect = null;

如下兩個(gè)字段保存調(diào)度優(yōu)先級(jí)相關(guān)的信息,會(huì)在講解Scheduler時(shí)介紹。

// 調(diào)度優(yōu)先級(jí)相關(guān)
this.lanes = NoLanes;
this.childLanes = NoLanes;

總結(jié)

在本節(jié)我們對(duì)Reconciler的架構(gòu)有了大概的認(rèn)知,了解了傳統(tǒng)的庫(kù)與現(xiàn)代框架的工作原理,也掌握了預(yù)編譯和即時(shí)編譯的區(qū)別,以及它們?cè)诂F(xiàn)代框架中的應(yīng)用。

在上一節(jié)中,我們實(shí)現(xiàn)了JSX的轉(zhuǎn)換,知道了React Element這種數(shù)據(jù),但是它也有一定的缺陷,為了解決這個(gè)缺陷,React 引入了Fiber架構(gòu),介紹了Fiber出現(xiàn)的意義,以及它的結(jié)構(gòu)是什么樣的,通過(guò)FiberNode組成的FiberNode Tree的結(jié)構(gòu)。

下一節(jié)主要介紹Fiber作為Reconciler核心模塊的工作單元,是如何創(chuàng)建及更新DOM的。

責(zé)任編輯:姜華 來(lái)源: 前端時(shí)光屋
相關(guān)推薦

2023-03-28 07:59:57

ReactReconciler

2024-04-24 11:00:05

React 18Fiber

2022-04-16 20:10:00

React Hookfiber框架

2021-06-16 06:05:25

React18React

2021-11-01 19:49:55

React組件模式

2023-12-01 09:14:58

ReactFiber

2021-06-22 07:45:57

React18startTransiReact

2022-03-16 17:01:35

React18并發(fā)的React組件render

2021-06-22 07:30:07

React18Automatic b自動(dòng)批處理

2022-04-27 07:37:42

ReactReact18

2022-03-30 14:22:55

ReactReact18并發(fā)特性

2022-07-06 08:30:36

vuereactvdom

2022-07-13 15:23:57

Vue fiberreact前端

2020-10-28 09:12:48

React架構(gòu)Hooks

2021-12-16 06:21:16

React組件前端

2021-11-29 06:05:31

React組件前端

2022-02-11 13:44:56

fiber架構(gòu)React

2022-05-03 21:18:38

Vue.js組件KeepAlive

2021-06-15 14:54:23

ReactReact 18SSR

2022-07-03 20:53:23

React18請(qǐng)求數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)