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

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

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

前言

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

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

單個(gè)節(jié)點(diǎn)的創(chuàng)建更新流程

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

圖片

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

比如:

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

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

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

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

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

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

圖片

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

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

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

什么是"雙緩存"?

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

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

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

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

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

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

雙緩存Fiber樹

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

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

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

雙緩存樹一個(gè)顯著的特點(diǎn)就是兩棵樹之間會(huì)互相切換,通過alternate屬性連接。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

圖片

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

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

mount 時(shí)有兩種情況:

  1. 整個(gè)應(yīng)用的首次渲染,這種情況發(fā)生首次進(jìn)入頁面時(shí)。
  2. 某個(gè)組件的首次渲染,當(dāng) isShow 為 true時(shí),Btn 組件進(jìn)入 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 時(shí)上面的Fiber樹構(gòu)建過程如下:

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

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

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

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

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

圖片

mount 首屏渲染階段

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

當(dāng)前僅有一個(gè)HostRootFiber,對(duì)應(yīng)"首屏渲染時(shí)只有根節(jié)點(diǎn)的空白畫面"。

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

render 生成workInProgress樹階段

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

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

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

圖片

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

commit 階段

等到頁面渲染完成時(shí),workInProgress Fiber樹會(huì)替換之前的current Fiber樹,進(jìn)而fiberRootNode的current指針會(huì)指向新的current Fiber樹。

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

過程如圖所示:

圖片

update 時(shí) Fiber Tree的更迭

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

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

圖片

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

圖片

render 階段的流程

接下來,我們來看看用原理,在源碼中它是如何實(shí)現(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階段的初期會(huì)依賴于Scheduler(調(diào)度器)的結(jié)果來判斷執(zhí)行哪個(gè)方法,比如Scheduler(調(diào)度器)會(huì)根據(jù)任務(wù)的優(yōu)先級(jí)選擇執(zhí)行performSyncWorkOnRoot或performConcurrentWorkOnRoot方法。這取決于任務(wù)的類型和優(yōu)先級(jí)。同步任務(wù)通常具有較高優(yōu)先級(jí),需要立即執(zhí)行,而并發(fā)任務(wù)會(huì)在空閑時(shí)間段執(zhí)行以避免阻塞主線程。

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

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

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

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

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

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

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

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

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

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

這里為什么指向的是 workInProgress 呢? 因?yàn)樵诿看武秩靖聲r(shí),即將展示到界面上的是 workInProgress 樹,只有在首屏渲染的時(shí)候它才為空。

render階段流程概覽

Fiber Reconciler是從Stack Reconciler重構(gòu)而來,通過遞歸遍歷的方式實(shí)現(xiàn)可中斷的遞歸。 因?yàn)榭梢园裵erformUnitOfWork方法分為兩部分:"遞"和"歸"。

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

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

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

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

舉一個(gè)例子:

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ù)組是遍歷過程中的順序,可以看到,遍歷的過程中會(huì)從應(yīng)用的根節(jié)點(diǎn)RootFiberNode開始,依次執(zhí)行beginWork和completeWork,最后形成一顆Fiber樹,每個(gè)節(jié)點(diǎn)以child和return項(xiàng)鏈。

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

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

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

知道了beginWork和completeWork它們是怎樣的流程后,我們?cè)賮砜此侨绾螌?shí)現(xiàn)的:

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

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

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

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

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

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

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

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

責(zé)任編輯:姜華 來源: 前端時(shí)光屋
相關(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組件模式

2021-06-22 07:45:57

React18startTransiReact

2022-12-19 08:17:36

ReactReconciler

2023-09-22 11:17:50

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

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

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

決策樹隨機(jī)森林CART模型

2020-10-28 09:12:48

React架構(gòu)Hooks

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ù)

2022-07-06 15:07:47

React開發(fā)

2022-04-18 08:57:32

React 18前端
點(diǎn)贊
收藏

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