偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

「從0實現(xiàn)React18系列」Reconciler架構(gòu)的雙緩存樹實現(xiàn)原理

開發(fā) 前端
對于同一個節(jié)點,React 會比較這個節(jié)點的ReactElement與FiberNode,生成子FiberNode。并根據(jù)比較的結(jié)果生成不同標(biāo)記(插入、刪除、移動...),對應(yīng)不同宿主環(huán)境API的執(zhí)行。

前言

通過??上一篇??文章的學(xué)習(xí),了解了Fiber是什么,知道了Fiber節(jié)點可以保存對應(yīng)的DOM節(jié)點。Fiber節(jié)點構(gòu)成的Fiber Tree會對應(yīng)DOM Tree。

前面也提到Fiber是一種新的調(diào)和算法,那么它是如何更新DOM節(jié)點的呢?

單個節(jié)點的創(chuàng)建更新流程

對于同一個節(jié)點,React 會比較這個節(jié)點的ReactElement與FiberNode,生成子FiberNode。并根據(jù)比較的結(jié)果生成不同標(biāo)記(插入、刪除、移動...),對應(yīng)不同宿主環(huán)境API的執(zhí)行。

圖片

根據(jù)上面的Reconciler的工作流程,舉一個例子:

比如:

mount階段,掛載<div></div>。

  1. 先通過jsx("div")生成 React Element <div></div>。
  2. 生成的對應(yīng)的fiberNode為null(由于是由于是掛載階段,React還未構(gòu)建組件樹)。
  3. 生成子fiberNode(實際上就是這個div的fiber節(jié)點)。
  4. 生成Placement標(biāo)記。

將<div></div>更新為<p></p>。

update階段,更新將<div></div>更新為<p></p>。

  1. 先通過jsx("p")生成 React Element <p></p>。
  2. p與對應(yīng)的fiberNode作比較(FiberNode {type: 'div'})。
  3. 生成子fiberNode為null。
  4. 生成對應(yīng)標(biāo)記Delement Placement。

用一張圖解釋上面的流程:

圖片

當(dāng)所有的ReactElement比較完后,會生成一顆fiberNode Tree,一共會存在兩棵fiberNode Tree。

  • current:與視圖中真實UI對應(yīng)的fiberNode樹。
  • workInProgress:觸發(fā)更新后,正在reconciler中計算的fiberNode Tree(用于下一次的視圖更新,在下一次視圖更新后,會變成current Tree)。

這就是React中的"雙緩存樹"技術(shù)。

什么是"雙緩存"?

雙緩存技術(shù)是一種計算機圖形學(xué)中用于減少屏幕閃爍和提高渲染性能的技術(shù)。

就好像你是一個畫家,你需要在一個畫布上繪制一幅畫。在沒有雙緩存技術(shù)的情況下,你會直接在畫布上作畫。當(dāng)你繪制一條線或一個形狀時,觀眾會立即看到這個過程。如果你的繪畫速度較慢,觀眾可能會看到畫面的閃爍和變化,這會導(dǎo)致視覺上的不舒適。

引入雙緩存技術(shù)就好比你有兩個畫布:一個是主畫布,觀眾可以看到它;另一個是隱藏畫布,觀眾看不到它。在這種情況下,你會在隱藏畫布上進行繪畫。當(dāng)你完成一個階段性的繪制任務(wù)后,你將隱藏畫布上的圖像瞬間復(fù)制到主畫布上。觀眾只能看到主畫布上的圖像,而看不到隱藏畫布上的繪制過程。這樣,即使你的繪畫速度較慢,觀眾也不會看到畫面的閃爍和變化,從而獲得更流暢的視覺體驗。

使用雙緩存技術(shù)時,計算機會在一個隱藏的緩沖區(qū)(后臺緩沖區(qū))上進行繪制,然后將繪制好的圖像一次性復(fù)制到屏幕上(前臺緩沖區(qū))。這樣可以減少屏幕閃爍,并提高渲染性能。

這種在內(nèi)存中構(gòu)建并直接替換的技術(shù)叫作雙緩存。

React 中使用"雙緩存"來完成Fiber Tree的構(gòu)建與替換,對應(yīng)著DOM Tree的創(chuàng)建于與更新。

雙緩存Fiber樹

Fiber架構(gòu)中同時存在兩棵Fiber Tree,一顆是"真實UI對應(yīng)的 Fiber Tree"可以理解為前緩沖區(qū)。另一課是"正在內(nèi)存中構(gòu)建的 Fiber Tree"可以理解為后緩沖區(qū),這里值宿主環(huán)境(比如瀏覽器)。

當(dāng)前屏幕上顯示內(nèi)容對應(yīng)的Fiber樹稱為current Fiber樹,正在內(nèi)存中構(gòu)建的Fiber樹稱為workInProgress Fiber樹。

current Fiber樹中的Fiber節(jié)點被稱為current fiber,workInProgress Fiber樹中的Fiber節(jié)點被稱為workInProgress fiber,他們通過alternate屬性連接。

雙緩存樹一個顯著的特點就是兩棵樹之間會互相切換,通過alternate屬性連接。

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;

雙緩存樹切換的規(guī)則

React應(yīng)用的根節(jié)點通過current指針在不同F(xiàn)iber樹的HostRootFiber根節(jié)點(ReactDOM.render創(chuàng)建的根節(jié)點)間切換。

  • 在 mount時(首次渲染),會根據(jù)jsx方法返回的React Element構(gòu)建Fiber對象,形成Fiber樹。
  • 然后這棵Fiber樹會作為current Fiber應(yīng)用到真實DOM上。
  • 在 update時(狀態(tài)更新),會根據(jù)狀態(tài)變更后的React Element和current Fiber作對比形成新的workInProgress Fiber樹。
  • 即當(dāng)workInProgress Fiber樹構(gòu)建完成交給Renderer(渲染器)渲染在頁面上后,應(yīng)用根節(jié)點的current指針指向workInProgress Fiber樹。
  • 然后workInProgress Fiber切換成current Fiber應(yīng)用到真實DOM上,這就達到了更新的目的。

這一切都是在內(nèi)存中發(fā)生的,從而減少了對DOM的直接操作。

每次狀態(tài)更新都會產(chǎn)生新的workInProgress Fiber樹,通過current與workInProgress的替換,完成DOM更新,這就是React中用的雙緩存樹切換規(guī)則。

Renderer 是一個與特定宿主環(huán)境(如瀏覽器 DOM、服務(wù)器端渲染、React Native 等)相關(guān)的模塊。Renderer 負(fù)責(zé)將 React 組件樹轉(zhuǎn)換為特定宿主環(huán)境下的實際 UI。從而使 React 能夠在多個平臺上運行。

上面的語言可能有些枯燥,我們來畫個圖演示一下。

比如有下面這樣一段代碼,點擊元素把div切換成p元素:

function App() {
const [elementType, setElementType] = useState('div');

const handleClick = () => {
setElementType(prevElementType => {
return prevElementType === 'div' ? 'p' : 'div';
})
}

// 根據(jù) elementType 的值動態(tài)創(chuàng)建對應(yīng)的元素
const Element = elementType;

return (
<div>
<Element onClick={handleClick}>
點擊我切換 div p 標(biāo)簽
</Element>
</div>
)
}

const root = document.querySelector("#root");
ReactDOM.createRoot(root).render(<App />);

圖片

接下來,我們分別從 mount(首次渲染)和 update(更新)兩個角度講解 Fiber 架構(gòu)的工作原理。

mount 時 Fiber Tree的構(gòu)建

mount 時有兩種情況:

  1. 整個應(yīng)用的首次渲染,這種情況發(fā)生首次進入頁面時。
  2. 某個組件的首次渲染,當(dāng) isShow 為 true時,Btn 組件進入 mount 首次渲染流程。
{isShow ? <Btn /> : null}

假如有這樣一段代碼:

function App() {
const [num, add] = useState(0);
return (
<p onClick={() => add(num + 1)}>{num}</p>
)
}

const root = document.querySelector("#root");
ReactDOM.createRoot(root).render(<App />)

mount 時上面的Fiber樹構(gòu)建過程如下:

  1. 首次執(zhí)行ReactDOM.createRoot(root)會創(chuàng)建fiberRootNode。
  2. 接著執(zhí)行到render(<App />)時會創(chuàng)建HostRootFiber,實際上它是一個HostRoot節(jié)點。

fiberRootNode 是整個應(yīng)用的根節(jié)點,HostRootFiber 是 <App /> 所在組件樹的根節(jié)點。

  1. 從HostRootFiber開始,以DFS(深度優(yōu)先搜索)的的順序遍歷子節(jié)點,以及生成對應(yīng)的FiberNode。
  2. 在遍歷過程中,為FiberNode標(biāo)記"代表不同副作用的 flags",以便后續(xù)在宿主環(huán)境中渲染的使用。

在上面我們之所以要區(qū)分fiberRootNode和HostRootFiber是因為在整個React應(yīng)用程序中開發(fā)者可以多次多次調(diào)用render方法渲染不同的組件樹,它們會有不同的HostRootFiber,但是整個應(yīng)用的根節(jié)點只有一個,那就是fiberRootNode。

執(zhí)行 ReactDOM.createRoot 會創(chuàng)建如圖所示結(jié)構(gòu):

圖片

mount 首屏渲染階段

由于是首屏渲染階段,頁面中還沒有掛載任何DOM節(jié)點,所以fiberRootNode.current指向的HostRootFiber沒有任何子Fiber節(jié)點(即current Fiber樹為空)。

當(dāng)前僅有一個HostRootFiber,對應(yīng)"首屏渲染時只有根節(jié)點的空白畫面"。

<body>
<div id="root"></div>
</body>

render 生成workInProgress樹階段

接下來進入render階段,根據(jù)組件返回的JSX在內(nèi)存中依次構(gòu)建創(chuàng)建Fiber節(jié)點并連接在一起構(gòu)建Fiber樹,被稱為workInProgress Fiber樹。

在構(gòu)建workInProgress Fiber樹時會嘗試復(fù)用current Fiber樹中已有的Fiber節(jié)點內(nèi)的屬性,(在首屏渲染時,只有HostRootFiber),也可以理解為首屏渲染時,它以自己的身份生成了一個workInProgress 樹只不過還是HostRootFiber(HostRootFiber.alternate。

基于DFS(深度優(yōu)先搜索)依次生成的workInProgress節(jié)點,并連接起來構(gòu)成wip 樹的過程如圖所示:

圖片

上圖中已構(gòu)建完的workInProgress Fiber樹會在commit階段被渲染到頁面。

commit 階段

等到頁面渲染完成時,workInProgress Fiber樹會替換之前的current Fiber樹,進而fiberRootNode的current指針會指向新的current Fiber樹。

完成雙緩存樹的切換工作,曾經(jīng)的Wip Fiber樹變?yōu)閏urrent Fiber樹。

過程如圖所示:

圖片

update 時 Fiber Tree的更迭

  1. 接下來我們點擊p節(jié)點觸發(fā)狀態(tài)改變。這會開啟一次新的render階段并構(gòu)建一課新的workInProgress Fiber樹。

和mount時一樣,workInProgress Fiber的創(chuàng)建可以復(fù)用current Fiber樹對應(yīng)節(jié)點的數(shù)據(jù),這個決定是否服用的過程就是Diff算法, 后面章節(jié)會詳細(xì)講解。

圖片

  1. workInProgress Fiber樹在render階段完成構(gòu)建后會進入commit階段渲染到頁面上。渲染完成后,workInProgress Fiber樹變?yōu)閏urrent Fiber樹。

圖片

render 階段的流程

接下來,我們來看看用原理,在源碼中它是如何實現(xiàn)的。

Reconciler工作的階段在 React 內(nèi)部被稱為 render 階段,ClassComponent 的render函數(shù)、Function Component函數(shù)本身也都在 render 階段被調(diào)用。

根據(jù)Scheduler調(diào)度的結(jié)果不同,render階段可能開始于performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的調(diào)用。

也就是說React在執(zhí)行render階段的初期會依賴于Scheduler(調(diào)度器)的結(jié)果來判斷執(zhí)行哪個方法,比如Scheduler(調(diào)度器)會根據(jù)任務(wù)的優(yōu)先級選擇執(zhí)行performSyncWorkOnRoot或performConcurrentWorkOnRoot方法。這取決于任務(wù)的類型和優(yōu)先級。同步任務(wù)通常具有較高優(yōu)先級,需要立即執(zhí)行,而并發(fā)任務(wù)會在空閑時間段執(zhí)行以避免阻塞主線程。

這里補充一下,調(diào)度器可能的執(zhí)行結(jié)果,以用來判斷執(zhí)行什么入口函數(shù):

如果不知道調(diào)度器的執(zhí)行結(jié)構(gòu)都有哪幾類,可以跳過這段代碼向下看:

現(xiàn)在還不需要學(xué)習(xí)這兩個方法,只需要知道在這兩個方法中會調(diào)用 performUnitOfWork方法就好。

// performSyncWorkOnRoot會調(diào)用該方法
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}

// performConcurrentWorkOnRoot會調(diào)用該方法
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}

可以看到,它們唯一的區(qū)別就是是否會調(diào)用shouldYield。如果當(dāng)前瀏覽器幀沒有剩余時間,shouldYield會終止循環(huán),直到瀏覽器有空閑時間再繼續(xù)遍歷。

也就說當(dāng)更新正在進行時,如果有 "更高優(yōu)先級的更新" 產(chǎn)生,則會終端當(dāng)前更新,優(yōu)先處理高優(yōu)先級更新。

高優(yōu)先級的更新比如:"鼠標(biāo)懸停","文本框輸入"等用戶更易感知的操作。

workInProgress代表當(dāng)前正在工作的一個fiberNode,它是一個全局的指針,指向當(dāng)前正在工作的 fiberNode,一般是workInProgress。

performUnitOfWork方法會創(chuàng)建下一個Fiber節(jié)點,并賦值給workInProgress,并將workInProgress與已經(jīng)創(chuàng)建好的Fiber節(jié)點連接起來構(gòu)成Fiber樹。

這里為什么指向的是 workInProgress 呢? 因為在每次渲染更新時,即將展示到界面上的是 workInProgress 樹,只有在首屏渲染的時候它才為空。

render階段流程概覽

Fiber Reconciler是從Stack Reconciler重構(gòu)而來,通過遞歸遍歷的方式實現(xiàn)可中斷的遞歸。 因為可以把performUnitOfWork方法分為兩部分:"遞"和"歸"。

"遞" 階段會從 HostRootFiber開始向下以 DFS 的方式遍歷,為遍歷到的每個fiberNode執(zhí)行beginWork方法。該方法會根據(jù)傳入的fiberNode創(chuàng)建下一級fiberNode。

當(dāng)遍歷到葉子元素(不包含子fiberNode)時,performUnitOfWork就會進入 "歸" 的階段。

"歸" 階段會調(diào)用completeWork方法處理fiberNode。當(dāng)某個fiberNode執(zhí)行完complete方法后,如果其存在兄弟fiberNode(fiberNode.sibling !== null),會進入其兄弟fiber的"遞階段"。如果不存在兄弟fiberNode,會進入父fiberNode的 "歸" 階段。

遞階段和歸階段會交錯執(zhí)行直至HostRootFiber的"歸"階段。到此,render階段的工作就結(jié)束了。

舉一個例子:

function App() {
return (
<div>
<p>1229</p>
jasonshu
</div>
)
}

const root = document.querySelector("#root");
ReactDOM.createRoot(root).render(<App />);

當(dāng)執(zhí)行完深度優(yōu)先搜索之后形成的workInProgress樹。

圖片

圖中的數(shù)組是遍歷過程中的順序,可以看到,遍歷的過程中會從應(yīng)用的根節(jié)點RootFiberNode開始,依次執(zhí)行beginWork和completeWork,最后形成一顆Fiber樹,每個節(jié)點以child和return項鏈。

注意:當(dāng)遍歷到只有一個子文本節(jié)點的Fiber時,該Fiber節(jié)點的子節(jié)點不會執(zhí)行beginWork和completeWork,如圖中的"jasonshu"文本節(jié)點。這是react的一種優(yōu)化手段

剛剛提到:workInProgress代表當(dāng)前正在工作的一個fiberNode,它是一個全局的指針,指向當(dāng)前正在工作的 fiberNode,一般是workInProgress。

// 該函數(shù)用于調(diào)度和執(zhí)行 FiberNode 樹的更新和渲染過程
// 該函數(shù)的作用是處理 React 程序中更新請求,計算 FiberNode 樹中的每個節(jié)點的變化,并把這些變化同步到瀏覽器的DOM中
function workLoop() {
while (workInProgress !== null) {
// 開始執(zhí)行每個工作單元的工作
performUmitOfWork(workInProgress);
}
}

知道了beginWork和completeWork它們是怎樣的流程后,我們再來看它是如何實現(xiàn)的:

這段代碼主要計算FiberNode節(jié)點的變化,更新workInProgress,beginWork函數(shù)的最初運行也是在下面這個函數(shù)中,同時它也完成遞和歸兩個階段的操作。

// 在這個函數(shù)中,React 會計算 FiberNode 節(jié)點的變化,并更新 workInProgress
function performUmitOfWork(fiber: FiberNode) {
// 如果有子節(jié)點,就一直遍歷子節(jié)點
const next = beginWork(fiber);
// 遞執(zhí)行完之后,需要更新下工作單元的props
fiber.memoizedProps = fiber.pendingProps;

// 沒有子節(jié)點的 FiberNode 了,代表遞歸到最深層了。
if (next === null) {
completeUnitOfWork(fiber);
} else {
// 如果有子節(jié)點的 FiberNode,則更新子節(jié)點為新的 fiberNode 繼續(xù)執(zhí)行
workInProgress = next;
}
}

在下面的函數(shù)中主要進行的操作:

// 主要進行歸的過程,向上遍歷父節(jié)點以及兄弟,更新它們節(jié)點的變化,并更新 workInProgress
function completeUnitOfWork(fiber: FiberNode) {
let node: FiberNode | null = fiber;

do {
// 歸:沒有子節(jié)點之后開始向上遍歷父節(jié)點
completeWork(node);
const sibling = node.sibling;
if (sibling !== null) {
// 有兄弟節(jié)點時,將指針指到兄弟節(jié)點
workInProgress = sibling;
return;
}
// 兄弟節(jié)點不存在時,遞歸應(yīng)該繼續(xù)往上指到父親節(jié)點
node = node.return;
workInProgress = node;
} while (node !== null);
}

到此,Reconciler的工作架構(gòu)架子我們就搭完了。

接下來我們來講在構(gòu)建過程中每個Fiber節(jié)點具體是如何創(chuàng)建的呢?在下一篇會詳細(xì)講解beginWork和completeWork是如何實現(xiàn)的?會正式進入render階段的實現(xiàn)了。

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

2023-03-21 08:31:13

ReconcilerFiber架構(gòu)

2021-06-16 06:05:25

React18React

2021-11-01 19:49:55

React組件模式

2022-12-19 08:17:36

ReactReconciler

2021-06-22 07:45:57

React18startTransiReact

2022-03-16 17:01:35

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

2023-09-22 11:17:50

紅黑樹結(jié)構(gòu)數(shù)據(jù)結(jié)構(gòu)

2021-06-22 07:30:07

React18Automatic b自動批處理

2022-04-27 07:37:42

ReactReact18

2024-04-24 11:00:05

React 18Fiber

2022-03-30 14:22:55

ReactReact18并發(fā)特性

2021-11-29 06:05:31

React組件前端

2022-07-06 08:30:36

vuereactvdom

2017-08-04 14:28:40

決策樹隨機森林CART模型

2020-10-28 09:12:48

React架構(gòu)Hooks

2025-10-20 07:21:15

2022-03-25 08:31:09

ReactReact 18升級

2022-05-03 21:18:38

Vue.js組件KeepAlive

2021-06-15 14:54:23

ReactReact 18SSR

2022-07-03 20:53:23

React18請求數(shù)據(jù)
點贊
收藏

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