React Hooks?是?React16.8 引入的一個新特性,它允許函數組件中使用?state?和其他 React 特性,而不必使用類組件。Hooks?是一個非常重要的概念,因為它們提供了更簡單、更易于理解的?React?開發體驗。 React Hooks?的核心源碼主要包括兩個部分:React?內部的?Hook?管理器和一系列預置的?Hook?函數。 首先,讓我們看一下?React?內部的?Hook?管理器。這個管理器是?React?內部的一個重要機制,它負責管理組件中的所有?Hook,并確保它們在組件渲染期間以正確的順序調用。
內部 Hook 管理器
示例:
const Hook = { queue: [], current: null, }; function useState(initialState) { const state = Hook.current[Hook.queue.length]; if (!state) { Hook.queue.push({ state: typeof initialState === 'function' ? initialState() : initialState, setState(value) { this.state = value; render(); }, }); } return [state.state, state.setState.bind(state)]; } function useHook(callback) { Hook.current = { __proto__: Hook.current, }; try { callback(); } finally { Hook.current = Hook.current.__proto__; } } function render() { useHook(() => { const [count, setCount] = useState(0); console.log('count:', count); setTimeout(() => { setCount(count + 1); }, 1000); }); } render(); 在這個示例中,Hook?對象有兩個重要屬性:queue?和?current。queue?存儲組件中所有?Hook?的狀態和更新函數,current?存儲當前正在渲染的組件的?Hook?鏈表。useState?和?useHook?函數則分別負責創建新的?Hook?狀態和在組件中使用?Hook。
預置 Hook 函數
useState Hook
以下是?useState Hook?的實現示例:
function useState(initialState) { const hook = updateWorkInProgressHook(); if (!hook.memoizedState) { hook.memoizedState = [ typeof initialState === 'function' ? initialState() : initialState, action => { hook.queue.pending = true; hook.queue.dispatch = action; scheduleWork(); }, ]; } return hook.memoizedState; }上述代碼實現了?useState Hook,其主要作用是返回一個?state?和更新函數的數組,state 初始值為?initialState。 在這個實現中,updateWorkInProgressHook()?函數用來獲取當前正在執行的函數組件的 fiber 對象并判斷是否存在對應的?hook。它的實現如下:
function updateWorkInProgressHook() { const fiber = getWorkInProgressFiber(); let hook = fiber.memoizedState; if (hook) { fiber.memoizedState = hook.next; hook.next = null; } else { hook = { memoizedState: null, queue: { pending: null, dispatch: null, last: null, }, next: null, }; } workInProgressHook = hook; return hook; }getWorkInProgressFiber()?函數用來獲取當前正在執行的函數組件的?fiber?對象,workInProgressHook?則用來存儲當前正在執行的?hook?對象。在函數組件中,每一個?useState?調用都會創建一個新的 hook 對象,并將其添加到?fiber?對象的?hooks?鏈表中。這個?hooks?鏈表是通過?fiber?對象的?memoizedState?屬性來維護的。 我們還需要注意到在?useState Hook?的實現中,每一個?hook?對象都包含了一個?queue?對象,用來存儲待更新的狀態以及更新函數。scheduleWork()?函數則用來通知?React?調度器有任務需要執行。 在?React?的源碼中,useState?函數實際上是一個叫做?useStateImpl?的內部函數。 下面是?useStateImpl?的源碼:
function useStateImpl可以看到,useStateImpl?函數的作用就是獲取當前的?dispatcher?并調用它的?useState?方法,返回一個數組,第一個元素是狀態的值,第二個元素是一個?dispatch?函數,用來更新狀態。這里的?resolveDispatcher?函數用來獲取當前的?dispatcher,其實現如下:(initialState: (() => S) | S): [S, Dispatch>] { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); }
function resolveDispatcher(): Dispatcher { const dispatcher = currentlyRenderingFiber?.dispatcher; if (dispatcher === undefined) { throw new Error('Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)'); } return dispatcher; }resolveDispatcher?函數首先嘗試獲取當前正在渲染的?fiber?對象的?dispatcher?屬性,如果獲取不到則說 明當前不在組件的渲染過程中,就會拋出一個錯誤。 最后,我們來看一下?useState?方法在具體的?dispatcher?實現中是如何實現的。我們以?useReducer?的 dispatcher?為例,它的實現如下:
export function useReducer可以看到,useReducer?方法實際上是調用了一個叫做?updateReducer?的函數,返回了一個包含當前狀態和?dispatch?函數的數組。updateReducer?的實現比較復雜,涉及到了很多細節,這里不再展開介紹。( reducer: (prevState: S, action: A) => S, initialState: S, initialAction?: A, ): [S, Dispatch] { const [dispatch, currentState] = updateReducer( reducer, // $FlowFixMe: Flow doesn't like mixed types [initialState, initialAction], // $FlowFixMe: Flow doesn't like mixed types reducer === basicStateReducer ? basicStateReducer : updateStateReducer, ); return [currentState, dispatch]; }
useEffect Hook
useEffect?是?React?中常用的一個?Hook?函數,用于在組件中執行副作用操作,例如訪問遠程數據、添加 / 移除事件監聽器、手動操作?DOM?等等。useEffect?的核心功能是在組件的渲染過程結束之后異步執行回調函數,它的實現方式涉及到 React 中的異步渲染機制。 以下是 useEffect Hook 的實現示例:
?
function useEffect(callback, dependencies) { // 通過調用 useLayoutEffect 或者 useEffect 方法來獲取當前的渲染批次 const batch = useContext(BatchContext); // 根據當前的渲染批次判斷是否需要執行回調函數 if (shouldFireEffect(batch, dependencies)) { callback(); } // 在組件被卸載時清除當前 effect 的狀態信息 return () => clearEffect(batch); }在這個示例中,useEffect?接收兩個參數:回調函數和依賴項數組。當依賴項數組中的任何一個值發生變化時, React?會在下一次渲染時重新執行?useEffect?中傳入的回調函數。 useEffect?函數的實現方式主要依賴于?React?中的異步渲染機制。當一個組件需要重新渲染時,React?會將所有的?state?更新操作加入到一個隊列中,在當前渲染批次結束之后再異步執行這些更新操作,從而避免在同一個渲染批次中連續執行多次更新操作。 在?useEffect?函數中,我們通過調用?useContext(BatchContext)?方法來獲取當前的渲染批次,并根據?shouldFireEffect?方法判斷是否需要執行回調函數。在回調函數執行完畢后,我們需要通過?clearEffect?方法來清除當前?effect?的狀態信息,避免對后續的渲染批次產生影響。
總結
總的來說,React Hooks?的實現原理并不復雜,它主要依賴于?React?內部的?fiber?數據結構和調度系統,通過這些機制來實現對組件狀態的管理和更新。Hooks?能夠讓我們在函數組件中使用狀態和其他?React?特性,使得函數組件的功能可以和類組件媲美。 除了?useState、useEffect?等?hook,React?還有?useContext?等常用的?Hook。它們的實現原理也基本相似,都是利用?fiber?架構來實現狀態管理和生命周期鉤子等功能。 以上是?hook?簡單實現示例,它們并不是?React?中實際使用的代碼,但是可以幫助我們更好地理解?hook?的核心實現方式。
編輯:黃飛
?
評論
查看更多