React Hooks源碼深度解析
作者:京東零售 鄭炳懿
前言
React Hooks
是React
16.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<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
可以看到,useStateImpl
函數的作用就是獲取當前的dispatcher
並調用它的useState
方法,返回一個數組,第一個元素是狀態的值,第二個元素是一個dispatch
函數,用來更新狀態。這裏的resolveDispatcher
函數用來獲取當前的dispatcher
,其實現如下:
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<S, A>(
reducer: (prevState: S, action: A) => S,
initialState: S,
initialAction?: A,
): [S, Dispatch<A>] {
const [dispatch, currentState] = updateReducer<S, A>(
reducer,
// $FlowFixMe: Flow doesn't like mixed types
[initialState, initialAction],
// $FlowFixMe: Flow doesn't like mixed types
reducer === basicStateReducer ? basicStateReducer : updateStateReducer,
);
return [currentState, dispatch];
}
可以看到,useReducer
方法實際上是調用了一個叫做updateReducer
的函數,返回了一個包含當前狀態和dispatch
函數的數組。updateReducer
的實現比較複雜,涉及到了很多細節,這裏不再展開介紹。
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
的核心實現方式。
- 應用健康度隱患刨析解決系列之數據庫時區設置
- 對於Vue3和Ts的心得和思考
- 一文詳解擴散模型:DDPM
- zookeeper的Leader選舉源碼解析
- 一文帶你搞懂如何優化慢SQL
- 京東金融Android瘦身探索與實踐
- 微前端框架single-spa子應用加載解析
- cookie時效無限延長方案
- 聊聊前端性能指標那些事兒
- Spring竟然可以創建“重複”名稱的bean?—一次項目中存在多個bean名稱重複問題的排查
- 京東金融Android瘦身探索與實踐
- Spring源碼核心剖析
- 深入淺出RPC服務 | 不同層的網絡協議
- 安全測試之探索windows遊戲掃雷
- 關於數據庫分庫分表的一點想法
- 對於Vue3和Ts的心得和思考
- Bitmap、RoaringBitmap原理分析
- 京東小程序CI工具實踐
- 測試用例設計指南
- 當你對 redis 説你中意的女孩是 Mia