React19源碼系列之Hooks(useId)

useId的介紹

https://zh-hans.react.dev/reference/react/useId

useId 是 React 18 引入的一個新 Hook,主要用于生成全局唯一的 ID。在開發中,我們經常需要為元素(如表單元素、模態框等)生成唯一 ID,以便在 JavaScript 中進行操作或在 CSS 中進行樣式綁定。使用 useId 可以避免手動管理 ID 帶來的問題,比如在服務器端渲染(SSR)時客戶端和服務器端生成的 ID 不一致。

useId的使用

例子:基本用法

import React, { useId } from 'react';function App() {const id = useId();return (<div><label htmlFor={id}>用戶名:</label><input type="text" id={id} /></div>);
}export default App;

例子:處理多個ID

import React, { useId } from 'react';function App() {const baseId = useId();const nameId = `${baseId}-name`;const emailId = `${baseId}-email`;return (<form><label htmlFor={nameId}>姓名:</label><input type="text" id={nameId} /><br /><label htmlFor={emailId}>郵箱:</label><input type="email" id={emailId} /></form>);
}export default App;

例子:在組件樹中使用
useId 在組件樹中使用時,每個組件實例都會生成不同的 ID。這意味著即使在嵌套組件中多次使用 useId,也不會產生沖突

import React, { useId } from 'react';function InputWithLabel({ labelText }) {const id = useId();return (<div><label htmlFor={id}>{labelText}</label><input type="text" id={id} /></div>);
}function App() {return (<div><InputWithLabel labelText="用戶名" /><InputWithLabel labelText="密碼" /></div>);
}export default App;

hook 初始化入口

hook是函數組件中的鉤子函數。在函數組件渲染的過程中會調用renderWithHooks函數。

renderWithHooks

函數參數含義:

  • current:舊的 Fiber 節點,如果是首次渲染則為 null。
  • workInProgress:當前正在處理的新 Fiber 節點。
  • Component:要渲染的函數式組件,它接收 props 和 secondArg 作為參數,并返回 JSX 元素。
  • props:傳遞給組件的屬性。
  • secondArg:額外的參數。
  • nextRenderLanes:下一次渲染的優先級車道。
function renderWithHooks<Props, SecondArg>(current: Fiber | null,workInProgress: Fiber,Component: (p: Props, arg: SecondArg) => any,props: Props,secondArg: SecondArg,nextRenderLanes: Lanes,
): any {// 確定當前渲染的優先級。renderLanes = nextRenderLanes;// 將 currentlyRenderingFiber 指向 workInProgress,表示當前正在渲染這個 Fiber 節點。currentlyRenderingFiber = workInProgress;// 重置信息workInProgress.memoizedState = null;workInProgress.updateQueue = null;workInProgress.lanes = NoLanes;//鉤子調度器,區分是初始化還是更新ReactSharedInternals.H =current === null || current.memoizedState === null? HooksDispatcherOnMount: HooksDispatcherOnUpdate;//調用函數組件let children = Component(props, secondArg);// 調用 finishRenderingHooks 函數,進行一些渲染完成后的清理和處理工作,例如處理副作用鉤子的執行等。finishRenderingHooks(current, workInProgress, Component);return children;
}
// renderLanes:表示當前渲染的優先級車道,初始值為 NoLanes(通常為 0),用于標識渲染任務的優先級。
let renderLanes: Lanes = NoLanes;//默認0// 指向當前正在渲染的 Fiber 節點,初始為 null。Fiber 是 React 中的一種數據結構,用于表示組件樹中的每個節點。
let currentlyRenderingFiber: Fiber = (null: any);// 當前的鉤子
let currentHook: Hook | null = null;//正在處理的鉤子
let workInProgressHook: Hook | null = null;

所以初始化用HooksDispatcherOnMount,更新使用HooksDispatcherOnUpdate

HooksDispatcherOnMount

const HooksDispatcherOnMount: Dispatcher = {readContext,use,useCallback: mountCallback,useContext: readContext,useEffect: mountEffect,useImperativeHandle: mountImperativeHandle,useLayoutEffect: mountLayoutEffect,useInsertionEffect: mountInsertionEffect,useMemo: mountMemo,useReducer: mountReducer,useRef: mountRef,useState: mountState,useDebugValue: mountDebugValue,useDeferredValue: mountDeferredValue,useTransition: mountTransition,useSyncExternalStore: mountSyncExternalStore,useId: mountId,
};

HooksDispatcherOnUpdate

const HooksDispatcherOnUpdate: Dispatcher = {readContext,use,useCallback: updateCallback,useContext: readContext,useEffect: updateEffect,useImperativeHandle: updateImperativeHandle,useInsertionEffect: updateInsertionEffect,useLayoutEffect: updateLayoutEffect,useMemo: updateMemo,useReducer: updateReducer,useRef: updateRef,useState: updateState,useDebugValue: updateDebugValue,useDeferredValue: updateDeferredValue,useTransition: updateTransition,useSyncExternalStore: updateSyncExternalStore,useId: updateId,
};

useId初始化

function useId(): string {
//resolveDispatcher 是 React 內部的一個函數,它的作用是獲取當前的調度器(dispatcher)const dispatcher = resolveDispatcher();// dispatcher.useId() 調用了調度器對象的 useId 方法。這個方法會生成一個全局唯一的 ID 字符串,并將其返回。return dispatcher.useId();
}

resolveDispatcher

function resolveDispatcher() {// 從 ReactSharedInternals 對象中獲取名為 H 的屬性,并將其賦值給變量 dispatcher。ReactSharedInternals 是 React 內部使用的一個共享對象,它包含了一些 React 運行時的關鍵信息和工具。H 屬性在這里代表著當前 React 環境下的調度器實例。const dispatcher = ReactSharedInternals.H;// 將獲取到的調度器實例 dispatcher 返回給調用者。這樣,調用 resolveDispatcher 函數的代碼就可以使用這個調度器來執行各種調度相關的操作,比如調用調度器的 useState 方法來處理狀態管理。return dispatcher;
}

mountId

mountId 函數是 React 中 useId Hook 在掛載階段(組件首次渲染)用于生成全局唯一 ID 的核心實現。該函數會根據當前是服務端渲染(SSR)還是客戶端渲染的不同情況,生成不同格式的唯一 ID,并將其存儲在 Fiber 節點對應的 hook 對象的 memoizedState 屬性上,最后返回這個唯一 ID。

function mountId(): string {//創建 hook 對象,將 hook 對象添加到 workInProgressHook 單向鏈表中,返回最新的 hook 鏈表const hook = mountWorkInProgressHook();
//getWorkInProgressRoot() 方法獲取當前的 FiberRoot 對象const root = ((getWorkInProgressRoot(): any): FiberRoot);//從 FiberRoot 對象 上獲取id前綴const identifierPrefix = root.identifierPrefix;let id;// 調用 getIsHydrating() 方法判斷是服務端渲染還是客戶端渲染if (getIsHydrating()) {//服務端渲染注水(hydrate)階段生成的唯一 id,以冒號開頭,并以冒號結尾,使用大寫字母 R 標識該id是服務端渲染生成的id//獲取組件樹的idconst treeId = getTreeId();// Use a captial R prefix for server-generated ids.id = ':' + identifierPrefix + 'R' + treeId;// localIdCounter 變量記錄組件中 useId 的執行次數const localId = localIdCounter++;if (localId > 0) {id += 'H' + localId.toString(32);}id += ':';} else {//客戶端渲染生成的唯一 id,以冒號開頭,并以冒號結尾,使用小寫字母 r 標識該id 是客戶端渲染生成的id//全局變量 globalClientIdCounter 記錄 useId hook 在組件中的調用次數const globalClientId = globalClientIdCounter++;// globalClientIdCounter初始值為0id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';}// 將生成的唯一 id 存儲到 hook 對象的 memoizedState 屬性上hook.memoizedState = id;return id;
}

以下代碼可以看到useId的返回值的格式

id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
// ':r0:'

如何在掛載的時候加入配置項identifierPrefix

ReactDOM.createRoot(document.getElementById('box'),{identifierPrefix: 'testyoyo'
});
id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':';
//:testyoyor0:

updateId

當函數組件刷新時,重新構建hook鏈表時,遇到useId會執行以下代碼

function updateId(): string {//  獲取當前的 workInProgressHookconst hook = updateWorkInProgressHook();// 從當前的 workInProgressHook 上獲取 useId 生成的 id,因此即使組件重新渲染,id 也不會變化const id: string = hook.memoizedState;return id;
}

工具函數 mountWorkInProgressHook

mountWorkInProgressHook 函數主要用于在 React 的渲染過程中,為當前正在處理的 Fiber 節點(currentlyRenderingFiber)掛載一個新的 Hook。
Hook 是 React 中用于在函數組件中使用狀態和副作用的機制,這個函數負責初始化 Hook 對象,并將其添加到當前 Fiber 節點的 Hook 鏈表中。

function mountWorkInProgressHook(): Hook {// 初始化hook對象const hook: Hook = {memoizedState: null,//用于存儲當前 Hook 的狀態值,在使用 useState 或 useReducer 等鉤子時會更新這個值。baseState: null,// 表示狀態的基礎值,在處理更新隊列時會用到。baseQueue: null,// 存儲尚未處理的更新隊列,通常與 baseState 配合使用。queue: null,// 存儲當前 Hook 的更新隊列,用于存儲狀態更新的操作。next: null,// 用于將多個 Hook 對象連接成一個鏈表,指向下一個 Hook 對象。};// 如果 workInProgressHook 為 null,說明這是當前 Fiber 節點的第一個 Hook。if (workInProgressHook === null) {// 將 currentlyRenderingFiber 的 memoizedState 屬性設置為新創建的 Hook 對象,并將 workInProgressHook 也指向這個 Hook 對象。這樣,currentlyRenderingFiber 的 memoizedState 就成為了 Hook 鏈表的頭節點。currentlyRenderingFiber.memoizedState = workInProgressHook = hook;} else {// Append to the end of the list// 如果 workInProgressHook 不為 null,說明當前 Fiber 節點已經有其他 Hook 存在。此時,將新創建的 Hook 對象添加到 Hook 鏈表的末尾。workInProgressHook = workInProgressHook.next = hook;}// 返回當前正在處理的 Hook 對象return workInProgressHook;
}

在這里插入圖片描述

export type Hook = {memoizedState: any,baseState: any,baseQueue: Update<any, any> | null,queue: any,next: Hook | null,
};

當前Fiber 節點與 hook 鏈表的關聯關系圖

在這里插入圖片描述

工具函數 updateWorkInProgressHook

updateWorkInProgressHook 函數是 React 內部用于更新和管理 Hook 的核心函數之一。在 React 的更新過程中,組件可能會重新渲染,而 Hook 也需要相應地更新。這個函數的主要目的是在新舊 Fiber 節點的 Hook 鏈表之間進行同步和復用,確保 Hook 的狀態能夠正確地傳遞和更新。

function updateWorkInProgressHook(): Hook {// 確定下一個當前 Hook(nextCurrentHook)let nextCurrentHook: null | Hook;// currentHook表示當前正在處理的舊 Hook// 當 currentHook 為 null 時,說明是處理第一個 Hook。if (currentHook === null) {// currentlyRenderingFiber.alternate 指向的是上一次渲染的 Fiber 節點(雙緩沖機制中的舊 Fiber)const current = currentlyRenderingFiber.alternate;// 如果該舊 Fiber 存在,就從其 memoizedState 中獲取第一個舊 Hook;if (current !== null) {nextCurrentHook = current.memoizedState;// fiber存在的hookl鏈表} else {// 若不存在,則 nextCurrentHook 為 null。nextCurrentHook = null;}} else {// 當 currentHook 不為 null 時,nextCurrentHook 就是當前舊 Hook 的下一個 Hook。nextCurrentHook = currentHook.next;}// 確定下一個工作中的 Hook(nextWorkInProgressHook)let nextWorkInProgressHook: null | Hook;// workInProgressHook 表示當前正在構建的新 Fiber 節點中的 Hook。if (workInProgressHook === null) {// 若 workInProgressHook 為 null,說明是處理第一個 Hook,則從當前正在渲染的 Fiber 節點的 memoizedState 中獲取第一個 Hook 作為 nextWorkInProgressHooknextWorkInProgressHook = currentlyRenderingFiber.memoizedState;} else {// 若不為 null,則 nextWorkInProgressHook 就是當前 workInProgressHook 的下一個 Hook。nextWorkInProgressHook = workInProgressHook.next;}// 若 nextWorkInProgressHook 不為 null,說明已經有正在構建的 Hook 可以復用。if (nextWorkInProgressHook !== null) {// 將 workInProgressHook 更新為 nextWorkInProgressHook,并更新 nextWorkInProgressHook 為其下一個 Hook。workInProgressHook = nextWorkInProgressHook;nextWorkInProgressHook = workInProgressHook.next;// 同時,將 currentHook 更新為 nextCurrentHook。currentHook = nextCurrentHook;} else {// 若 nextWorkInProgressHook 為 null,說明沒有現成的工作中的 Hook 可以復用,需要克隆舊 Hook 到新 Hook。if (nextCurrentHook === null) {const currentFiber = currentlyRenderingFiber.alternate;}
// 先將 currentHook 更新為 nextCurrentHook。currentHook = nextCurrentHook;// 創建一個新的 Hook 對象 newHook,將舊 Hook 的 memoizedState、baseState、baseQueue 和 queue 復制到新 Hook 中。const newHook: Hook = {memoizedState: currentHook.memoizedState,baseState: currentHook.baseState,baseQueue: currentHook.baseQueue,queue: currentHook.queue,next: null,};// 如果 workInProgressHook 為 null,說明這是 Hook 鏈表的第一個 Hookif (workInProgressHook === null) {// This is the first hook in the list.// 將 currentlyRenderingFiber.memoizedState 和 workInProgressHook 都指向新 Hook;currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;} else {// Append to the end of the list.// 若不為 null,則將新 Hook 添加到 Hook 鏈表的末尾。workInProgressHook = workInProgressHook.next = newHook;}}// 返回當前工作中的 Hookreturn workInProgressHook;
}

在這里插入圖片描述

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

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

相關文章

經典面試題:C/C++中static關鍵字的三大核心作用與實戰應用

一、修飾局部變量&#xff1a;改變生命周期&#xff0c;保留跨調用狀態 核心作用&#xff1a; ?延長生命周期&#xff1a;將局部變量從棧區移至靜態存儲區&#xff08;數據段或BSS段&#xff09;&#xff0c;生命周期與程序一致?保留狀態&#xff1a;變量在函數多次調用間保…

Redisson 分布式鎖原理

加鎖原理 # 如果鎖不存在 if (redis.call(exists, KEYS[1]) 0) then# hash結構,鎖名稱為key,線程唯一標識為itemKey&#xff0c;itemValue為一個計數器。支持相同客戶端線程可重入,每次加鎖計數器1.redis.call(hincrby, KEYS[1], ARGV[2], 1);# 設置過期時間redis.call(pexpi…

【數據結構】棧與隊列:基礎 + 競賽高頻算法實操(含代碼實現)

什么是棧&#xff1f;什么是隊列&#xff1f; 什么是先進后出&#xff1f;什么是先進先出&#xff1f; 了解基礎之后&#xff0c;又如何用來寫算法題&#xff1f; 帶著這些疑問&#xff0c;讓我帶領你&#xff0c;走進棧與隊列的世界 棧與隊列 棧&#xff1a; 1、棧的基本…

單元化架構在字節跳動的落地實踐

資料來源&#xff1a;火山引擎-開發者社區 什么是單元化 單元化的核心理念是將業務按照某種維度劃分成一個個單元&#xff0c; 理想情況下每個單元內部都是完成所有業務操作的自包含集合&#xff0c;能獨立處理業務流程&#xff0c;各個單元均有其中一部分數據&#xff0c;所有…

基于Python的垃圾短信分類

垃圾短信分類 1 垃圾短信分類問題介紹 1.1 垃圾短信 隨著移動互聯科技的高速發展&#xff0c;信息技術在不斷改變著我們的生活&#xff0c;讓我們的生活更方便&#xff0c;其中移動通信技術己經在我們生活起到至關重要的作用&#xff0c;與我們每個人人息息相關。短信作為移…

leetcode1971.尋找圖中是否存在路徑

初嘗并查集&#xff0c;直接套用模板 class Solution { private:vector<int> father;void init() {for(int i0;i<father.size();i)father[i]i;}int find(int v) {return vfather[v]?v:father[v]find(father[v]);//路徑壓縮}bool isSame(int u,int v){ufind(u);vfind…

QAI AppBuilder 快速上手(7):目標檢測應用實例

YOLOv8_det是YOLO 系列目標檢測模型&#xff0c;專為高效、準確地檢測圖像中的物體而設計。該模型通過引入新的功能和改進點&#xff0c;如因式分解卷積&#xff08;factorized convolutions&#xff09;和批量歸一化&#xff08;batch normalization&#xff09;&#xff0c;在…

景聯文科技:以高質量數據標注推動人工智能領域創新與發展

在當今這個由數據驅動的時代&#xff0c;高質量的數據標注對于推動機器學習、自然語言處理&#xff08;NLP&#xff09;、計算機視覺等領域的發展具有不可替代的重要性。數據標注過程涉及對原始數據進行加工&#xff0c;通過標注特定對象的特征來生成能夠被機器學習模型識別和使…

MySQL 索引下推

概念 索引下推&#xff08;Index Condition Pushdown&#xff0c;簡稱 ICP&#xff09; 是 MySQL 5.6 版本中提供的一項索引優化功能&#xff0c;它允許存儲引擎在索引遍歷過程中&#xff0c;執行部分 WHERE字句的判斷條件&#xff0c;直接過濾掉不滿足條件的記錄&#xff0c;…

NVIDIA Dynamo源碼編譯

Ref https://github.com/PyO3/maturin Rust 程序設計語言 代碼庫&#xff1a; https://github.com/ai-dynamo/dynamo https://github.com/ai-dynamo/nixl dynamo/container/Dockerfile.vllm 相關whl包 官方提供了4個whl包 ai_dynamo # 這個包ubuntu 22.04也可以用&…

【Android】安卓原生應用播放背景音樂與音效(筆記)

本文提供完整的音頻管理器代碼&#xff0c;涵蓋了背景音樂&#xff08;BGM&#xff09;和短音效的播放控制。無論是游戲中的音效&#xff0c;還是應用中的背景音樂&#xff0c;通過 AudioManager&#xff0c;你可以方便地管理和控制音頻資源。 前言 在 Android 開發中&#xf…

Unity | 游戲數據配置

目錄 一、ScriptableObject 1.創建ScriptableObject 2.創建asset資源 3.asset資源的讀取與保存 二、Excel轉JSON 1.Excel格式 2.導表工具 (1)處理A格式Excel (2)處理B格式Excel 三、解析Json文件 1.讀取test.json文件 四、相關插件 在游戲開發中,策劃…

2025信創即時通訊排行:安全合規與生態適配雙輪驅動

隨著信息技術應用創新&#xff08;信創&#xff09;戰略的深化&#xff0c;國產即時通訊工具在政企市場的滲透率顯著提升。2025年作為“十四五”規劃收官之年&#xff0c;信創產業迎來規模化應用關鍵節點。本文將從認證標準、市場表現、技術架構、行業適配四大維度&#xff0c;…

關于TVS管漏電流的問題?

問題描述&#xff1a; 在量產的帶電池故事機生產中&#xff0c;工廠產線測試電流時&#xff0c;有1臺機器電流比正常機器大10mA左右。 原因分析&#xff1a; 1、分析電路原理圖&#xff0c;去除可能出現問題的電壓或器件&#xff08;不影響系統&#xff09;&#xff0c;發現…

RAG 架構地基工程-Retrieval 模塊的系統設計分享

目錄 一、知識注入的關鍵前奏——RAG 系統中的檢索綜述 &#xff08;一&#xff09;模塊定位&#xff1a;連接語言模型與知識世界的橋梁 &#xff08;二&#xff09;核心任務&#xff1a;四大關鍵問題的協調解法 &#xff08;三&#xff09;系統特征&#xff1a;性能、精度…

Java-servlet(七)詳細講解Servlet注解

Java-servlet&#xff08;七&#xff09;詳細講解Servlet注解 前言一、注解的基本概念二、Override 注解2.1 作用與優勢2.2 示例代碼 三、Target 注解3.1 定義與用途3.2 示例代碼 四、WebServlet 注解4.1 作用4.2 示例代碼 五、反射與注解5.1 反射的概念5.2 注解與反射的結合使…

機器學習——分類、回歸、聚類、LASSO回歸、Ridge回歸(自用)

糾正自己的誤區&#xff1a;機器學習是一個大范圍&#xff0c;并不是一個小的方向&#xff0c;比如&#xff1a;線性回歸預測、卷積神經網絡和強化學都是機器學習算法在不同場景的應用。 機器學習最為關鍵的是要有數據&#xff0c;也就是數據集 名詞解釋&#xff1a;數據集中的…

本地AI大模型工具箱 Your local AI toolkit:LMStudio

LMStudio介紹 官網&#xff1a;LM Studio - Discover, download, and run local LLMs LMStudio 是一個面向機器學習和自然語言處理的&#xff0c;旨在使開發者更容易構建和部署AI語言模型的應用軟件。 LMStudio的特點是&#xff1a; 完全本地離線運行AI大模型 可以從Huggi…

[OpenCV】相機標定之棋盤格角點檢測與繪制

在OpenCV中&#xff0c;棋盤格角點檢測與繪制是一個常見的任務&#xff0c;通常用于相機標定。 棋盤格自定義可參考: OpenCV: Create calibration pattern 目錄 1. 棋盤格角點檢測 findChessboardCorners()2. 棋盤格角點繪制 drawChessboardCorners()3. 代碼示例C版本python版本…

redis的典型應用 --緩存

Redis最主要的用途&#xff0c;分為三個方面&#xff1a; 1.存儲數據&#xff08;內存數據庫&#xff09; 2.緩存&#xff08;最常用&#xff09; 3.消息隊列 緩存 (cache) 是計算機中的?個經典的概念。核?思路就是把?些常?的數據放到觸?可及(訪問速度更快)的地?&…