react中的fiber和初次渲染

源碼中定義了不同類型節點的枚舉值

組件類型

  • 文本節點
  • HTML標簽節點
  • 函數組件
  • 類組件
  • 等等

src/react/packages/react-reconciler/src/ReactWorkTags.js

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;

什么是fiber

A Fiber is work on a Component that needs to be done or was done. There can be more than one per component.

fiber是指組件上將要完成或者已經完成的任務,每個組件可以一個或者多個。

// 比如一個函數組件FunctionComponent 里面是
<div className="border"><p>段落</p><button>按鈕</button>
</div>
// 那最后的fiber結構
const fiber_ = {type: "div",props: {className: "border",},child: {// 第一個子節點type: "p",props: { children: "段落" },sibling: {// 下一個兄弟節點type: "button",props: { children: "按鈕" },},},
};

fiber結構

在這里插入圖片描述

為什么需要fiber

  1. 為什么需要fiber

    對于大型項目,組件樹會很大,這個時候遞歸遍歷的成本就會很高,會造成主線程被持續占用,結果就是主線程上的布局、動畫等周期性任務就無法立即得到處理,造成視覺上的卡頓,影響用戶體驗。

  2. 任務分解的意義

    解決上面的問題

  3. 增量渲染(把渲染任務拆分成塊,勻到多幀)

  4. 更新時能夠暫停,終止,復用渲染任務

  5. 給不同類型的更新賦予優先級

  6. 并發方面新的基礎能力

  7. 更流暢

創建fiber結構

fiber就是一個js對象來抽象vnode

function createFiber(vnode, returnFiber) {const fiber = {type: vnode.type,key: vnode.key,stateNode: null, // 原生標簽時候指dom節點,類組件時候指的是實例props: vnode.props,child: null, // 第一個子fibersibling: null, // 下一個兄弟fiberreturn: returnFiber, // 父節點// 標記節點是什么類型的flags: Placement,deletions: null, // 要刪除子節點 null或者[]index: null, //當前層級下的下標,從0開始// 記錄上一次的狀態 函數組件和類組件不一樣memorizedState: null,// old fiberalternate: null,};const { type } = vnode;if (isStr(type)) {// 原生標簽fiber.tag = HostComponent;} else if (isFn(type)) {// 函數組件或者是類組件fiber.tag = type.prototype.isComponent ? ClassComponent : FunctionComponent;} else if (isUndefined(type)) {fiber.tag = HostText;fiber.props = { children: vnode };} else {fiber.tag = Fragment;}return fiber;
}

深度優先遍歷每個fiber

對不同的類型節點tag,都有對應的處理方法

function performUnitOfWork() {const { tag } = wip;switch (tag) {// 原生標簽 比如div span button p acase HostComponent:updateHostComponent(wip);break;case FunctionComponent:updateFunctionComponent(wip);break;case ClassComponent:updateClassComponent(wip);break;case Fragment:updateFragmentComponent(wip);break;case HostText:updateHostTextComponent(wip);break;default:break;}if (wip.child) {wip = wip.child;return;}let next = wip;while (next) {if (next.sibling) {wip = next.sibling;return;}next = next.return;}wip = null;
}

初次渲染

在react項目中我們都是通過以下方法來初始化組件

ReactDOM.createRoot(document.getElementById("root")).render(jsx);

那我們就來實現一下該createRoot和render方法

源碼中的render是掛載到了原型對象上

// react-dom
import createFiber from "./ReactFiber";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";// 構造函數
function ReactDOMRoot(internalRoot) {this._internalRoot = internalRoot;
}ReactDOMRoot.prototype.render = function (children) {// 最原始的vnode節點(jsx) 我們需要的是fiber結構的vnodeconst root = this._internalRoot;// 原生dom節點console.log(root, "root");updateContainer(children, root);
};// 初次渲染 組件到g根dom節點上
function updateContainer(element, container) {const { containerInfo } = container;const fiber = createFiber(element, {type: containerInfo.nodeName.toLocaleLowerCase(),stateNode: containerInfo,});// 組件初次渲染scheduleUpdateOnFiber(fiber);
}
function createRoot(container) {const root = { containerInfo: container };return new ReactDOMRoot(root);
}// 一整個文件是ReactDOM, createRoot是ReactDOM上的一個方法
export default { createRoot };

scheduleUpdateOnFiber方法實現

觸發任務調度方法,來執行fiber的生成performUnitOfWork和commit提交兩個步驟

scheduleCallback是借助了MessageChannel方法來從最小堆中取優先級最高的任務來執行,此處暫時表示執行workLoop方法

// import scheduleCallback from '...todo'
export function scheduleUpdateOnFiber(fiber) {wip = fiber;wipRoot = fiber;scheduleCallback(workLoop);// scheduleCallback(() => {//   console.log("scheduleCallback1");// });// scheduleCallback(() => {//   console.log("scheduleCallback2");// });// scheduleCallback(() => {//   console.log("scheduleCallback3");// });// scheduleCallback(() => {//   console.log("scheduleCallbac4");// });
}function workLoop() {//協調while (wip) {performUnitOfWork();}//提交if (!wip && wipRoot) {commitWork();}
}
  1. 根據最原始的 vnode 節點(jsx) 調用 createFiber 方法生成我們需要的 fiber 結構的 vnode
    這一塊已經實現了
const fiber = createFiber(element, {type: containerInfo.nodeName.toLocaleLowerCase(),stateNode: containerInfo,});
  1. 根據 fiber 上不同 tag 屬性調用不同的 fiber 渲染方法 該方法里面調用了 reconcileChildren 方法(協調 children 生成 fiber 鏈表) 遞歸生成 fiber 單鏈表結構

以函數組件為例:

export function updateFunctionComponent(wip) {renderWithHooks(wip);// 函數組件的type是個函數 直接執行拿到childrenconst { type, props } = wip;// 子節點const children = type(props);reconcileChildren(wip, children);
}

reconcileChildren方法就是協調,協調所有后代節點生成fiber單鏈表結構

// 協調children生成fiber鏈表
export function reconcileChildren(returnFiber, children) {const newChildren = isArray(children) ? children : [children];// old fiber頭節點let oldFiber = returnFiber.alternate?.child;//   為啥去掉這句就不能渲染了 todo ...? 現在不會了 但是會出現兩個相同的元素if (isStringOrNumber(children)) {return;}// 實現fiber的鏈表結構let previousNewFiber = null;let newIndex = 0;for (newIndex = 0; newIndex < newChildren.length; newIndex++) {const newChild = newChildren[newIndex];// 如果newChil為null,會在createFiber中報錯if (newChild === null) {continue;}const newFiber = createFiber(newChild, returnFiber);const same = sameNode(newFiber, oldFiber);// 更新復用if (same) {Object.assign(newFiber, {stateNode: oldFiber.stateNode,alternate: oldFiber,flags: Update, // 默認是Placement 新增});}if (!same && oldFiber) {// 刪除節點deleteChild(returnFiber, oldFiber);}// ?? todo...if (oldFiber) {oldFiber = oldFiber.sibling;}// 第一個子fiber 好比nexIndex===0if (previousNewFiber === null) {returnFiber.child = newFiber;} else {previousNewFiber.sibling = newFiber;}// 記錄一下上次的fiberpreviousNewFiber = newFiber;}if (newIndex === newChildren.length) {deleteRemainingChildren(returnFiber, oldFiber);return;}
}
  1. 處理完所有 fiber 和 子 fiber 后,開始往 root 節點里面進行遞歸提交,包括提交自己,第一個子節點,第一個子節點的兄弟節點(增刪改查)的操作 調用了 commitRoot(commitWork)方法

  2. 根據 flags 屬性來判斷是新增 還是更新 還是刪除

    1. 新增則調用 dom 元素的 appendChild 方法
    2. 更新則根據新老節點對比 調用 updateNode 方法
    3. 刪除則調用 commitDeletion 通過 removeChild(父 dom 和子 dom)來刪除
function commitWork(wip) {if (!wip) {return false;}// 1.更新自己const { flags, stateNode, type } = wip;// 追加if (flags & Placement && stateNode) {// 函數組件prop.children的父級是函數組件名 再往上就是root根節點// const parentNode = wip.return.stateNode;const parentNode = getParentNode(wip.return);parentNode.appendChild(stateNode);}// 更新if (flags & Update && stateNode) {updateNode(stateNode, wip.alternate.props, wip.props);}// 刪除if (wip.deletions) {// 通過父節點來刪除commitDeletion(wip.deletions, stateNode || parentNode);}// 2.更新子節點commitWork(wip.child);// 3.更新兄弟節點commitWork(wip.sibling);
}
  1. 初始化結束

更新(更新操作無非就是 useState,useReducer 等改變了組件狀態而導致更新)

所以在 hook 函數里 我們需要去調用 scheduleUpdateOnFiber 方法來出觸發組件更新
然后回到了上面初次渲染一樣的邏輯

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/72617.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72617.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/72617.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Wireshark的OSPF報文抓包和分析(單區域ospf實驗)

一、OSPF的5種數據包和7種狀態機制 數據包 Hello&#xff1a;發現、建立鄰居&#xff08;鄰接&#xff09;關系、維持、周期保活&#xff1b;存在全網唯一的RID&#xff0c;使用IP地址表示 DBD&#xff1a;本地的數據庫的目錄&#xff08;摘要&#xff09;&#xff0c;LSDB的…

前后分離文件上傳案例,前端HTML,后端Net6開發的webapi(完整源代碼)下載

文件上傳功能在項目開發中非常實用&#xff0c;本案例前端用HTML頁面的form表單實現&#xff0c;后端用Net6實現。 前后分離文件上傳案例&#xff0c;前端HTML&#xff0c;后端Net6&#xff08;完整源代碼&#xff09; 下載鏈接https://download.csdn.net/download/luckyext/9…

Linux之命令記錄【一】

文章目錄 前言幾個重要的熱鍵1.[Tab]按鍵2.[Ctrl]-c 按鍵3.[Ctrl]-d 按鍵4.[shift]{[PageUP]|[Page Down]}按鍵 線上求助&#xff08;查看幫助信息&#xff09;1. --help2.man page3.info page 用戶身份1.su 基礎指令1.date2.cal3.bc 系統字符集相關1.locale 文本編輯器1.nano …

Unity HDR顏色、基礎顏色、強度強度、HDR面板Intensity之間的相互轉換

目錄 前言&#xff1a; 一、UnityHDR面板的規律 二、HDR與基礎顏色轉換&#xff0c;HDR強度獲取&#xff0c;輸入設置強度獲取 1.基礎色->HDR顏色 2.HDR顏色->基礎色 3.獲取HDR顏色在面板中的強度 4.獲取HDR顏色在面板設置輸入時的強度 前言&#xff1a; HDR&#…

T41LQ專為人工智能物聯網(AIoT)應用設計,適用于智能安防、智能家居、機器視覺等領域 軟硬件資料+樣品測試

君正&#xff08;Ingenic&#xff09;T系列芯片涵蓋多個型號&#xff0c;每個型號根據不同應用需求提供了多個版本。以下是各型號及其主要版本&#xff1a; 1. T23系列&#xff1a; T23N&#xff1a;標準版&#xff0c;適用于移動攝像機、安全監控、視頻通話和視頻分析等應用…

高頻 SQL 50 題(基礎版)| 高級字符串函數 / 正則表達式 / 子句:1667. 修復表中的名字、1527. 患某種疾病的患者、196. 刪除重復的電子郵箱、176. 第二高的薪水、...

高級字符串函數 / 正則表達式 / 子句 1667. 修復表中的名字 題目鏈接&#xff1a;1667. 修復表中的名字 狀態&#xff1a;學會了 思路&#xff1a; 要求修復名字&#xff08;首字母大寫&#xff0c;其他字母小寫&#xff09;&#xff0c;按順序返回。 想法就是取出名字這一列&…

《異步江湖:XHR、Promise 與 Event Loop 的恩怨情仇》

XMLHttpRequest XMLHttpRequest&#xff08;簡稱 XHR&#xff09;是瀏覽器提供的一個 JavaScript 對象&#xff0c;用于在客戶端和服務器之間發送 HTTP 請求。它是實現 AJAX&#xff08;Asynchronous JavaScript and XML&#xff09; 技術的核心工具&#xff0c;允許網頁在不…

C++課程設計【宿舍管理查詢軟件】

宿舍管理查詢軟件 一、題目描述二、源碼以及說明宿舍管理查詢軟件設計與實現1. 系統設計思路1.1 功能需求1.2 數據結構2. 系統實現3. 代碼說明3.1 數據結構3.2 功能實現3.3 文件存儲4. 示例運行輸入輸出5. 總結其他QT文章推薦一、題目描述 (一)問題描述 為宿舍管理人員編寫一…

MWC 2025 | 移遠通信推出AI智能無人零售解決方案,以“動態視覺+邊緣計算”引領智能零售新潮流

在無人零售市場蓬勃發展的浪潮中&#xff0c;自動售貨機正經歷著從傳統機械式操作向AI視覺技術的重大跨越。 移遠通信作為全球領先的物聯網整體解決方案供應商&#xff0c;精準把握行業趨勢&#xff0c;在2025世界移動通信大會&#xff08;MWC&#xff09;上宣布推出全新AI智能…

C語言常用的頭文件,include文件

常用頭文件功能速覽 1 &#xff0c;通用常用頭文件 01. stdio.h——標準輸入輸出 02. stdlib.h——內存管理與分配、隨機數、字符串轉換 03. string.h——字符串處理 04. math.h——數學 05. time.h——時間和日期 06. ctype…

[MySQL初階]MySQL(4)基本查詢

標題&#xff1a;[MySQL初階]MySQL&#xff08;4&#xff09;基本查詢 水墨不寫bug 文章目錄 一. 數據表設計二、對數據表的操作1. Create 操作&#xff08;插入數據&#xff09;查看最近受影響的行數&#xff1a; 2. Retrieve 操作&#xff08;讀取數據&#xff09;&#xff0…

小米智能音箱Pro搭載“超級小愛”,支持遠程控車

大家好,今天我要給大家好好嘮嘮小米智能音箱Pro,尤其是它搭載的“超級小愛”,那功能可太強大了,還支持遠程控車,真的是給我們的生活帶來了超多便利和驚喜。 先來說說這小米智能音箱Pro的外觀。它的設計非常簡約時尚,整體造型方方正正,線條流暢,放在家里任何一個角落都…

react中的useContext--為什么使用(一)

React 的數據傳遞流程 在 React 中&#xff0c;數據傳遞通常是自上而下的&#xff0c;也就是父組件把數據通過 props 傳遞給子組件&#xff0c;子組件無法直接修改父組件的數據。 例子&#xff1a;父組件向子組件傳遞數據 const Parent () > {const user { name: &quo…

如何使用 LLM 生成的術語自動在搜索應用程序上構建 autocomplete 功能

作者&#xff1a;來自 Elastic Michael Supangkat 了解如何在 Elastic Cloud 中&#xff0c;通過使用 LLM 生成的詞匯&#xff0c;為搜索應用增強自動補全功能&#xff0c;實現更智能、更動態的搜索建議。 自動補全是搜索應用中的一項關鍵功能&#xff0c;它通過在用戶輸入時實…

MAVEN手動配置(阿里云)全教程

介于網上各種各樣的MAVEN配置過程中方法大致相同卻細節參差不齊&#xff0c;我總結了我遇見的一些問題&#xff0c;來完全的解決MAVEN手動配置的全過程&#xff0c;以及分享解決小毛病的經驗。 所需材料&#xff1a; MAVEN3.9.9&#xff08;下載適合自己的版本即可&#xff09…

DeepSeek 3FS:端到端無緩存的存儲新范式

在 2025 年 2 月 28 日&#xff0c;DeepSeek 正式開源了其高性能分布式文件系統 3FS【1】&#xff0c;作為其開源周的壓軸項目&#xff0c;3FS 一經發布便引發了技術圈的熱烈討論。它不僅繼承了分布式存儲的經典設計&#xff0c;還通過極簡卻高效的架構&#xff0c;展現了存儲技…

HarmonyOS:如何將圖片轉為PixelMap并進行圖片緩存策略

前言&#xff1a;在HarmonyOS項目開發中&#xff0c;我們使用Ark-Ts語言開發項目。我們有個功能是拍照&#xff0c;除了正常顯示出來&#xff0c;并且上傳服務器。我在開發過程中&#xff0c;遇到的問題是&#xff0c;如果離開這個頁面再回到當前頁面仍要顯示圖片&#xff0c;那…

2025.3.9機器學習筆記:文獻閱讀

2025.3.9周報 一、文獻閱讀題目信息摘要Abstract創新點網絡架構實驗結論不足以及展望 一、文獻閱讀 題目信息 題目&#xff1a; Time-series generative adversarial networks for flood forecasting期刊&#xff1a; Journal of Hydrology作者&#xff1a; Peiyao Weng, Yu …

linux固定IP并解決虛擬機無法ping其他電腦問題

linux固定IP并解決虛擬機無法ping其他電腦問題 1.找到網卡文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33 2.編輯文件信息 BOOTPROTO 這個dhcp改為static#添加以下內容IPADDR<你的IP地址>NETMASK<子網掩碼>&#xff0c;例如255.255.255.0。GATEWAY<網…

Spring實戰spring-ai運行

目錄 1. 配置 2 .搭建項目 3. 查看對應依賴 3.1 OpenAI 依賴 3.2 配置 OpenAI API 密鑰 application.properties application.yml 4. openai實戰 5. 運行和測試 6. 高級配置 示例&#xff1a;配置模型和參數 解釋&#xff1a; 7. 處理異常和錯誤 示例&#xff1a;…