React Hooks 內部實現原理與函數組件更新機制
Hooks 的內部實現原理
React Hooks 的實現依賴于以下幾個關鍵機制:
1. 鏈表結構存儲 Hook 狀態
React 使用單向鏈表來管理 Hooks 的狀態。每個 Hook 節點包含:
type Hook = {memoizedState: any, // 存儲當前狀態baseState: any, // 基礎狀態baseQueue: Update<any, any> | null, // 基礎更新隊列queue: UpdateQueue<any, any> | null, // 更新隊列next: Hook | null, // 指向下一個 Hook
};
2. 當前 Hook 指針
React 內部維護一個 currentHook
指針,它會隨著組件的渲染過程依次指向鏈表中的每個 Hook:
let currentlyRenderingFiber: Fiber | null = null;
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
3. Hooks 調用順序的重要性
Hooks 必須無條件地在組件頂層調用,這是因為 React 依賴于調用順序來正確關聯 Hook 和它的狀態:
function updateFunctionComponent(fiber) {// 重置指針currentlyRenderingFiber = fiber;fiber.memoizedHooks = null;currentHook = null;workInProgressHook = null;// 執行組件函數const children = Component(props);// 渲染完成后重置currentlyRenderingFiber = null;currentHook = null;workInProgressHook = null;return children;
}
函數組件更新機制
1. 調度階段
當狀態更新時,React 會:
- 創建一個更新對象并加入更新隊列
- 調度一次新的渲染(通過
scheduleUpdateOnFiber
)
2. 渲染階段
在渲染階段,React 會:
- 調用函數組件
- 按順序執行 Hooks
- 返回新的 React 元素
function renderWithHooks(current, workInProgress, Component, props) {// 設置當前正在渲染的 FibercurrentlyRenderingFiber = workInProgress;// 重置 Hook 鏈表workInProgress.memoizedState = null;// 執行組件函數const children = Component(props);// 重置狀態currentlyRenderingFiber = null;return children;
}
3. 提交階段
在提交階段,React 會將渲染結果應用到 DOM 上,并執行副作用(useEffect 等)。
常見 Hook 的實現原理
useState
function useState(initialState) {return useReducer(basicStateReducer,initialState);
}function basicStateReducer(state, action) {return typeof action === 'function' ? action(state) : action;
}
useEffect
function useEffect(create, deps) {const fiber = currentlyRenderingFiber;const effect = {tag: HookEffectTag, // 標識是 effectcreate, // 副作用函數destroy: undefined, // 清理函數deps, // 依賴數組next: null, // 下一個 effect};// 將 effect 添加到 fiber 的 updateQueueif (fiber.updateQueue === null) {fiber.updateQueue = { lastEffect: null };}const lastEffect = fiber.updateQueue.lastEffect;if (lastEffect === null) {fiber.updateQueue.lastEffect = effect.next = effect;} else {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;fiber.updateQueue.lastEffect = effect;}
}
useMemo
function useMemo(nextCreate, deps) {const hook = mountWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;const nextValue = nextCreate();hook.memoizedState = [nextValue, nextDeps];return nextValue;
}
關鍵點總結
-
Hooks 依賴于調用順序:React 使用鏈表結構按順序存儲 Hook 狀態,因此不能在條件或循環中使用 Hooks。
-
雙緩存技術:React 使用 current 和 workInProgress 兩棵樹來實現異步可中斷的渲染。
-
閉包陷阱:函數組件每次渲染都會創建新的閉包,這解釋了為什么有時候會拿到舊的 state 或 props。
-
批量更新:React 會合并多個狀態更新,避免不必要的重復渲染。
-
副作用調度:useEffect 的副作用會在瀏覽器完成布局與繪制之后延遲執行。
理解這些原理有助于編寫更高效、更可靠的 React 應用,并能更好地調試 Hook 相關的問題。