日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
「從0實現(xiàn)React18系列」Reconciler架構的雙緩存樹實現(xiàn)原理

前言

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

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

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

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

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

比如:

mount階段,掛載

。

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

更新為

。

update階段,更新將

更新為

。

  1. 先通過jsx("p")生成 React Element

    。
  2. p與對應的fiberNode作比較(FiberNode {type: 'div'})。
  3. 生成子fiberNode為null。
  4. 生成對應標記Delement Placement。

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

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

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

這就是React中的"雙緩存樹"技術。

什么是"雙緩存"?

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

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

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

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

這種在內存中構建并直接替換的技術叫作雙緩存。

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

雙緩存Fiber樹

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

當前屏幕上顯示內容對應的Fiber樹稱為current Fiber樹,正在內存中構建的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應用的根節(jié)點通過current指針在不同F(xiàn)iber樹的HostRootFiber根節(jié)點(ReactDOM.render創(chuàng)建的根節(jié)點)間切換。

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

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

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

Renderer 是一個與特定宿主環(huán)境(如瀏覽器 DOM、服務器端渲染、React Native 等)相關的模塊。Renderer 負責將 React 組件樹轉換為特定宿主環(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)建對應的元素
const Element = elementType;

return (


點擊我切換 div 和 p 標簽


)
}

const root = document.querySelector("#root");
ReactDOM.createRoot(root).render();

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

mount 時 Fiber Tree的構建

mount 時有兩種情況:

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

假如有這樣一段代碼:

function App() {
const [num, add] = useState(0);
return (

add(num + 1)}>{num}


)
}

const root = document.querySelector("#root");
ReactDOM.createRoot(root).render()

mount 時上面的Fiber樹構建過程如下:

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

fiberRootNode 是整個應用的根節(jié)點,HostRootFiber 是  所在組件樹的根節(jié)點。

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

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

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

mount 首屏渲染階段

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

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



render 生成workInProgress樹階段

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

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

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

上圖中已構建完的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階段并構建一課新的workInProgress Fiber樹。

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

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

render 階段的流程

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

render階段流程概覽

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

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

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

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

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

舉一個例子:

function App() {
return (

1229


jasonshu

)
}

const root = document.querySelector("#root");
ReactDOM.createRoot(root).render();

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

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

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

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

// 該函數(shù)用于調度和執(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é)點不存在時,遞歸應該繼續(xù)往上指到父親節(jié)點
node = node.return;
workInProgress = node;
} while (node !== null);
}

到此,Reconciler的工作架構架子我們就搭完了。

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


文章名稱:「從0實現(xiàn)React18系列」Reconciler架構的雙緩存樹實現(xiàn)原理
當前URL:http://www.5511xx.com/article/cccjggg.html