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



















評論