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;
}