React Hooks?是?React16.8 引入的一個新特性,它允許函數(shù)組件中使用?state?和其他 React 特性,而不必使用類組件。Hooks?是一個非常重要的概念,因?yàn)樗鼈兲峁┝烁唵巍⒏子诶斫獾?React?開發(fā)體驗(yàn)。 React Hooks?的核心源碼主要包括兩個部分:React?內(nèi)部的?Hook?管理器和一系列預(yù)置的?Hook?函數(shù)。 首先,讓我們看一下?React?內(nèi)部的?Hook?管理器。這個管理器是?React?內(nèi)部的一個重要機(jī)制,它負(fù)責(zé)管理組件中的所有?Hook,并確保它們在組件渲染期間以正確的順序調(diào)用。
內(nèi)部 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?的狀態(tài)和更新函數(shù),current?存儲當(dāng)前正在渲染的組件的?Hook?鏈表。useState?和?useHook?函數(shù)則分別負(fù)責(zé)創(chuàng)建新的?Hook?狀態(tài)和在組件中使用?Hook。
預(yù)置 Hook 函數(shù)
useState Hook
以下是?useState Hook?的實(shí)現(xiàn)示例:
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; }上述代碼實(shí)現(xiàn)了?useState Hook,其主要作用是返回一個?state?和更新函數(shù)的數(shù)組,state 初始值為?initialState。 在這個實(shí)現(xiàn)中,updateWorkInProgressHook()?函數(shù)用來獲取當(dāng)前正在執(zhí)行的函數(shù)組件的 fiber 對象并判斷是否存在對應(yīng)的?hook。它的實(shí)現(xiàn)如下:
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()?函數(shù)用來獲取當(dāng)前正在執(zhí)行的函數(shù)組件的?fiber?對象,workInProgressHook?則用來存儲當(dāng)前正在執(zhí)行的?hook?對象。在函數(shù)組件中,每一個?useState?調(diào)用都會創(chuàng)建一個新的 hook 對象,并將其添加到?fiber?對象的?hooks?鏈表中。這個?hooks?鏈表是通過?fiber?對象的?memoizedState?屬性來維護(hù)的。 我們還需要注意到在?useState Hook?的實(shí)現(xiàn)中,每一個?hook?對象都包含了一個?queue?對象,用來存儲待更新的狀態(tài)以及更新函數(shù)。scheduleWork()?函數(shù)則用來通知?React?調(diào)度器有任務(wù)需要執(zhí)行。 在?React?的源碼中,useState?函數(shù)實(shí)際上是一個叫做?useStateImpl?的內(nèi)部函數(shù)。 下面是?useStateImpl?的源碼:
function useStateImpl可以看到,useStateImpl?函數(shù)的作用就是獲取當(dāng)前的?dispatcher?并調(diào)用它的?useState?方法,返回一個數(shù)組,第一個元素是狀態(tài)的值,第二個元素是一個?dispatch?函數(shù),用來更新狀態(tài)。這里的?resolveDispatcher?函數(shù)用來獲取當(dāng)前的?dispatcher,其實(shí)現(xiàn)如下:(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?函數(shù)首先嘗試獲取當(dāng)前正在渲染的?fiber?對象的?dispatcher?屬性,如果獲取不到則說 明當(dāng)前不在組件的渲染過程中,就會拋出一個錯誤。 最后,我們來看一下?useState?方法在具體的?dispatcher?實(shí)現(xiàn)中是如何實(shí)現(xiàn)的。我們以?useReducer?的 dispatcher?為例,它的實(shí)現(xiàn)如下:
export function useReducer可以看到,useReducer?方法實(shí)際上是調(diào)用了一個叫做?updateReducer?的函數(shù),返回了一個包含當(dāng)前狀態(tài)和?dispatch?函數(shù)的數(shù)組。updateReducer?的實(shí)現(xiàn)比較復(fù)雜,涉及到了很多細(xì)節(jié),這里不再展開介紹。( 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?函數(shù),用于在組件中執(zhí)行副作用操作,例如訪問遠(yuǎn)程數(shù)據(jù)、添加 / 移除事件監(jiān)聽器、手動操作?DOM?等等。useEffect?的核心功能是在組件的渲染過程結(jié)束之后異步執(zhí)行回調(diào)函數(shù),它的實(shí)現(xiàn)方式涉及到 React 中的異步渲染機(jī)制。 以下是 useEffect Hook 的實(shí)現(xiàn)示例:
?
function useEffect(callback, dependencies) { // 通過調(diào)用 useLayoutEffect 或者 useEffect 方法來獲取當(dāng)前的渲染批次 const batch = useContext(BatchContext); // 根據(jù)當(dāng)前的渲染批次判斷是否需要執(zhí)行回調(diào)函數(shù) if (shouldFireEffect(batch, dependencies)) { callback(); } // 在組件被卸載時清除當(dāng)前 effect 的狀態(tài)信息 return () => clearEffect(batch); }在這個示例中,useEffect?接收兩個參數(shù):回調(diào)函數(shù)和依賴項(xiàng)數(shù)組。當(dāng)依賴項(xiàng)數(shù)組中的任何一個值發(fā)生變化時, React?會在下一次渲染時重新執(zhí)行?useEffect?中傳入的回調(diào)函數(shù)。 useEffect?函數(shù)的實(shí)現(xiàn)方式主要依賴于?React?中的異步渲染機(jī)制。當(dāng)一個組件需要重新渲染時,React?會將所有的?state?更新操作加入到一個隊(duì)列中,在當(dāng)前渲染批次結(jié)束之后再異步執(zhí)行這些更新操作,從而避免在同一個渲染批次中連續(xù)執(zhí)行多次更新操作。 在?useEffect?函數(shù)中,我們通過調(diào)用?useContext(BatchContext)?方法來獲取當(dāng)前的渲染批次,并根據(jù)?shouldFireEffect?方法判斷是否需要執(zhí)行回調(diào)函數(shù)。在回調(diào)函數(shù)執(zhí)行完畢后,我們需要通過?clearEffect?方法來清除當(dāng)前?effect?的狀態(tài)信息,避免對后續(xù)的渲染批次產(chǎn)生影響。
總結(jié)
總的來說,React Hooks?的實(shí)現(xiàn)原理并不復(fù)雜,它主要依賴于?React?內(nèi)部的?fiber?數(shù)據(jù)結(jié)構(gòu)和調(diào)度系統(tǒng),通過這些機(jī)制來實(shí)現(xiàn)對組件狀態(tài)的管理和更新。Hooks?能夠讓我們在函數(shù)組件中使用狀態(tài)和其他?React?特性,使得函數(shù)組件的功能可以和類組件媲美。 除了?useState、useEffect?等?hook,React?還有?useContext?等常用的?Hook。它們的實(shí)現(xiàn)原理也基本相似,都是利用?fiber?架構(gòu)來實(shí)現(xiàn)狀態(tài)管理和生命周期鉤子等功能。 以上是?hook?簡單實(shí)現(xiàn)示例,它們并不是?React?中實(shí)際使用的代碼,但是可以幫助我們更好地理解?hook?的核心實(shí)現(xiàn)方式。
編輯:黃飛
?
評論