您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“react fiber執(zhí)行原理是什么”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“react fiber執(zhí)行原理是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。
在 react16
引入 Fiber
架構(gòu)之前,react 會(huì)采用遞歸方法對(duì)比兩顆虛擬DOM樹,找出需要改動(dòng)的節(jié)點(diǎn),然后同步更新它們,這個(gè)過程 react
稱為reconcilation(協(xié)調(diào))。在reconcilation
期間,react
會(huì)同步執(zhí)行操作,提交到真實(shí) DOM 的更改,會(huì)一直占著瀏覽器的資源,不能中斷,中斷后就不能恢復(fù),使得我們一些用戶操作定時(shí)器等等事件無法得到響應(yīng),是一個(gè)非常糟糕的用戶體驗(yàn)。
所以我們要解決的問題就是:解決React主線程長時(shí)間占用的一個(gè)問題。 這個(gè)時(shí)候,就引入了Fiber
架構(gòu)。
Fiber
可以理解為是一個(gè)執(zhí)行單元,也可以理解為是一種數(shù)據(jù)結(jié)構(gòu)。每一個(gè)React元素都對(duì)應(yīng)一個(gè)fiber對(duì)象,我們先看看fiber
中的屬性:
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // 作為靜態(tài)數(shù)據(jù)結(jié)構(gòu)的屬性 this.tag = tag; // Fiber對(duì)應(yīng)組件的類型 Function/Class/Host... this.key = key; // key屬性 this.elementType = null; // 大部分情況同type,某些情況不同,比如FunctionComponent使用React.memo包裹 this.type = null; // 對(duì)于 FunctionComponent,指函數(shù)本身,對(duì)于ClassComponent,指class,對(duì)于HostComponent,指DOM節(jié)點(diǎn)tagName this.stateNode = null; // Fiber對(duì)應(yīng)的真實(shí)DOM節(jié)點(diǎn) // 用于連接其他Fiber節(jié)點(diǎn)形成Fiber樹 this.parent = null; // 指向父級(jí)Fiber節(jié)點(diǎn) this.child = null; // 指向子Fiber節(jié)點(diǎn) this.sibling = null; // 指向右邊第一個(gè)兄弟Fiber節(jié)點(diǎn) this.index = 0; this.ref = null; // 作為動(dòng)態(tài)的工作單元的屬性 —— 保存本次更新造成的狀態(tài)改變相關(guān)信息 this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; // class 組件 Fiber 節(jié)點(diǎn)上的多個(gè) Update 會(huì)組成鏈表并被包含在 fiber.updateQueue 中。 函數(shù)組件則是存儲(chǔ) useEffect 的 effect 的環(huán)狀鏈表。 this.memoizedState = null; // hook 組成單向鏈表掛載的位置 this.dependencies = null; this.mode = mode; // Effects this.flags = NoFlags; this.subtreeFlags = NoFlags; this.deletions = null; // 調(diào)度優(yōu)先級(jí)相關(guān) this.lanes = NoLanes; this.childLanes = NoLanes; // 指向該fiber在另一次更新時(shí)對(duì)應(yīng)的fiber this.alternate = null; }
React Fiber 就是采用鏈表實(shí)現(xiàn)的,主要就是通過以下這幾個(gè)屬性表示:
this.parent = null; // 指向父級(jí)Fiber節(jié)點(diǎn) this.child = null; // 指向子Fiber節(jié)點(diǎn) this.sibling = null; // 指向右邊第一個(gè)兄弟Fiber節(jié)點(diǎn)
假如我們要渲染下面這個(gè)元素樹:
<div> <h2> <p> <a></a> </p> </h2> <h3></h3> </div>
我們看一下它的Fiber結(jié)構(gòu)樹:
每個(gè)fiber
元素都有這三個(gè)屬性,觀察上面圖發(fā)現(xiàn):
parent
:指向父級(jí)Fiber
節(jié)點(diǎn):
child
:指向子Fiber
節(jié)點(diǎn)
sibling
:指向右邊的兄弟節(jié)點(diǎn)
我們可以把每個(gè)fiber
當(dāng)做一個(gè)執(zhí)行單元,每次執(zhí)行完一個(gè)執(zhí)行單元。React
會(huì)去檢測還剩多少時(shí)間,如果沒有時(shí)間就將控制權(quán)讓給瀏覽器,如果還有時(shí)間就去執(zhí)行下一個(gè)執(zhí)行單元。
這里就涉及到了一個(gè)問題,react
如何和瀏覽器進(jìn)行控制權(quán)的交接,瀏覽器何時(shí)空閑呢?。我們先來了解一下瀏覽器的工作:
在瀏覽器中,我們所看到的頁面是一幀一幀畫出來的,渲染的幀率與設(shè)備的刷新率保持一致。通常情況下,我們的設(shè)備都是60Hz
,也就是說,1s
屏幕會(huì)刷新60
次。當(dāng)每秒內(nèi)繪制的幀數(shù)(FPS
)超過60
時(shí),頁面渲染是流暢的,當(dāng)幀數(shù)小于60
時(shí),會(huì)明顯感受到卡頓。下面來看完整的一幀中,瀏覽器具體做了哪些事情:
首先需要處理輸入事件,能夠讓用戶得到最早的反饋
接下來是處理定時(shí)器,需要檢查定時(shí)器是否到時(shí)間,并執(zhí)行對(duì)應(yīng)的回調(diào)
接下來處理 Begin Frame
(開始幀),即每一幀的事件,包括 window.resize
、scroll
、media query change
等
接下來執(zhí)行請(qǐng)求動(dòng)畫幀 requestAnimationFrame(rAF
),即在每次繪制之前,會(huì)執(zhí)行 rAF
回調(diào)
緊接著進(jìn)行 Layout
操作,包括計(jì)算布局和更新布局,即這個(gè)元素的樣式是怎樣的,它應(yīng)該在頁面如何展示
接著進(jìn)行 Paint
操作,得到樹中每個(gè)節(jié)點(diǎn)的尺寸與位置等信息,瀏覽器針對(duì)每個(gè)元素進(jìn)行內(nèi)容填充
到這時(shí)以上的六個(gè)階段都已經(jīng)完成了,接下來處于空閑階段(Idle Peroid
),可以在這時(shí)執(zhí)行 requestIdleCallback
里注冊(cè)的任務(wù)
這樣我們把工作單元的任務(wù)放到requestIdleCallback
回調(diào)當(dāng)中,如果瀏覽器處理完上述的任務(wù)(布局和繪制之后),還有盈余時(shí)間,這個(gè)時(shí)候就可以執(zhí)行我們的工作單元了。每次執(zhí)行完一個(gè)執(zhí)行單元。React
會(huì)去檢測還剩多少時(shí)間,如果沒有時(shí)間就將控制權(quán)讓給瀏覽器。直至,React
和瀏覽器通過合作式調(diào)度完美配合,實(shí)現(xiàn)高性能應(yīng)用。
從根節(jié)點(diǎn)開始調(diào)度和渲染可以分為兩個(gè)階段:render
和commit
。 先來了解下這幾個(gè)關(guān)鍵名詞:
workInProgress
代表當(dāng)前正在執(zhí)行更新的 Fiber
樹。在 setState
或者渲染 后,會(huì)構(gòu)建一顆 Fiber
樹,也就是 workInProgress tree
,
首次渲染之后,React
會(huì)生成一個(gè)對(duì)應(yīng)于 UI
渲染的 fiber
樹,稱之為 current 樹。在新一輪更新時(shí) workInProgress tree
再重新構(gòu)建,新workInProgress
的節(jié)點(diǎn)通過 alternate
屬性和 currentFiber
的節(jié)點(diǎn)建立聯(lián)系。
effect list
可以理解為是一個(gè)存儲(chǔ) effect
副作用列表容器。
在render
階段中,會(huì)找到所有節(jié)點(diǎn)的變更,比如說節(jié)點(diǎn)新增,編輯,刪除等等。這些變更React
稱之為副作用effect。在這個(gè)階段中,也可以認(rèn)為是diff
階段,主要就是對(duì)比currentFiber tree
和workInProgress tree
之間的差異,然后打上標(biāo)記
。
在這個(gè)階段,任務(wù)是可以終止的。React 可以根據(jù)當(dāng)前可用的時(shí)間片處理一個(gè)或多個(gè) fiber
節(jié)點(diǎn),并且得益于 fiber
對(duì)象中存儲(chǔ)的元素上下文信息以及構(gòu)成的鏈表結(jié)構(gòu),使其能夠?qū)?zhí)行到一半的工作仍保存在內(nèi)存的鏈表中。在重新獲得控制權(quán)后,又可以根據(jù)保存在內(nèi)存中的上下文信息快速找到停止的fiber
節(jié)點(diǎn),然后繼續(xù)工作執(zhí)行工作單元。
遍歷Fiber tree
時(shí)采用的是后序遍歷方法
從頂部開始遍歷
如果有child節(jié)點(diǎn),且還未遍歷,遍歷child節(jié)點(diǎn)
如果有child節(jié)點(diǎn),且已經(jīng)遍歷過,則遍歷sibling節(jié)點(diǎn)。
如果沒有child節(jié)點(diǎn),返回父節(jié)點(diǎn)
如果最后返回的節(jié)點(diǎn)為頂部,表示所有節(jié)點(diǎn)遍歷完成。
在遍歷的過程中,我們會(huì)去收集所有變更的節(jié)點(diǎn)產(chǎn)出的effect
,每個(gè)effect
通過鏈表的方式鏈接。每個(gè) fiber 有兩個(gè)屬性
firstEffect:指向第一個(gè)有副作用的子fiber
lastEffect:指向最后一個(gè)有副作用的子fiber
中間的使用nextEffect
做成一個(gè)單鏈表。
與render
階段不同,commit
階段是同步操作的。
因?yàn)樵?code>commit階段是更新真實(shí)的dom
,所以更新dom不可能一點(diǎn)一點(diǎn)去更新,這樣用戶體驗(yàn)會(huì)極差。所以commit
階段必須是同步執(zhí)行,一次更新到位。
首先的事情是遍歷effect-list
列表,拿到每一個(gè) effect
存儲(chǔ)的信息,根據(jù)副作用類型 effectTag
執(zhí)行相應(yīng)的處理并提交更新到真正的 DOM
。所有的effects
都會(huì)在layout phase
階段之前被處理。當(dāng)該階段執(zhí)行結(jié)束時(shí),workInProgress樹
會(huì)被替換成current樹
。到這里,根據(jù)收集到的變更信息完成了刷新操作。
讀到這里,這篇“react fiber執(zhí)行原理是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。