返回
从 useState 与 useReducer 源码入手,浅谈 Hooks 的运作原理
前端
2024-01-23 11:12:47
如何通过剖析源码深入理解 React Hooks
前言
React Hooks 是一项革命性的功能,极大地简化了 React 组件的编写。通过 Hooks,我们可以方便地管理组件状态、触发副作用以及与其他组件交互。然而,想要真正掌握 Hooks,仅仅了解其 API 是远远不够的。深入到源码之中,才能真正理解 Hooks 的运作原理。
useState 源码剖析
useState 是 React 中使用最为广泛的 Hooks 之一。它的作用是管理组件的状态。useState 的定义如下:
export default function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
- initialState :初始状态。可以是任何类型的值,包括基本类型、对象、数组或函数。
- 返回值 :一个数组,包含两个元素。第一个元素是当前状态,第二个元素是更新状态的函数。
useState 的工作原理
- 首先,useState 会检查当前组件是否已经挂载。如果组件还没有挂载,它将创建一个新的状态对象,并将其存储在组件的实例上。
- 然后,useState 会返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是更新状态的函数。
- 当调用更新状态的函数时,useState 会创建一个新的状态对象,并将其存储在组件的实例上。然后,它会触发组件的重新渲染。
useState 的源码解读
useState 的源码位于 react-dom/cjs/react-dom.development.js
文件中。下面我们来逐行解读 useState 的源码:
export default function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
- 第一行定义了 useState 函数的默认导出。
- 第二行是 useState 函数的主体。它接收一个参数 initialState,即初始状态。
- 第三行是 useState 函数的返回值。它返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是更新状态的函数。
function useState(initialState) {
// 如果组件未挂载,创建并存储一个新状态对象
if (currentlyRenderingFiber === null) {
// 创建一个新状态对象
const initialStateForHooks =
typeof initialState === 'function' ? initialState() : initialState;
// 将新状态对象存储在组件的实例上
currentlyRenderingFiber.memoizedState = [initialStateForHooks, dispatchAction];
return [initialStateForHooks, dispatchAction];
} else {
// 获取组件的当前状态
const currentHook = currentlyRenderingFiber.memoizedState;
// 如果当前状态不存在,创建一个新的状态对象
if (currentHook !== null) {
// 获取当前状态
const currentState = currentHook[0];
// 返回当前状态和更新状态的函数
return [currentState, currentHook[1]];
} else {
// 创建一个新状态对象
const initialStateForHooks =
typeof initialState === 'function' ? initialState() : initialState;
// 将新状态对象存储在组件的实例上
currentlyRenderingFiber.memoizedState = [initialStateForHooks, dispatchAction];
// 返回新状态对象和更新状态的函数
return [initialStateForHooks, dispatchAction];
}
}
}
- 第 3 行到第 16 行是 useState 函数的主体。它首先检查当前组件是否已经挂载。如果组件还没有挂载,它将创建一个新的状态对象,并将其存储在组件的实例上。
- 第 18 行到第 26 行是 useState 函数的返回值。它返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是更新状态的函数。
const dispatchAction = (action: SetStateAction<S>) => {
// 创建一个新的状态对象
const newState =
typeof action === 'function' ? (action(currentlyRenderingFiber.memoizedState[0])) : action;
// 将新状态对象存储在组件的实例上
currentlyRenderingFiber.memoizedState = [newState, dispatchAction];
// 触发组件的重新渲染
scheduleWork(currentlyRenderingFiber, RendererContext.Sync);
};
- 第 3 行定义了更新状态的函数 dispatchAction。
- 第 5 行到第 7 行是 dispatchAction 函数的主体。它首先创建一个新的状态对象。
- 第 9 行将新状态对象存储在组件的实例上。
- 第 11 行触发组件的重新渲染。
useReducer 源码剖析
useReducer 是另一个非常有用的 Hooks,它可以用来管理组件的复杂状态。useReducer 的定义如下:
export default function useReducer<S, A, I>(
reducer: Reducer<S, A>,
initialState: S | (() => S),
initialArg?: I,
): [S, Dispatch<A>];
- reducer :一个 reducer 函数,它接收当前状态和一个动作,并返回一个新的状态。
- initialState :初始状态。可以是任何类型的值,包括基本类型、对象、数组或函数。
- initialArg :一个可选的初始参数,它将作为第一个动作传递给 reducer 函数。
- 返回值 :一个数组,包含两个元素。第一个元素是当前状态,第二个元素是分发动作的函数。
useReducer 的工作原理
- 首先,useReducer 会检查当前组件是否已经挂载。如果组件还没有挂载,它将创建一个新的状态对象,并将其存储在组件的实例上。
- 然后,useReducer 会返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是分发动作的函数。
- 当调用分发动作的函数时,useReducer 会调用 reducer 函数,并用新的状态对象更新组件的实例。然后,它会触发组件的重新渲染。
useReducer 的源码解读
useReducer 的源码位于 react-dom/cjs/react-dom.development.js
文件中。下面我们来逐行解读 useReducer 的源码:
export default function useReducer<S, A, I>(
reducer: Reducer<S, A>,
initialState: S | (() => S),
initialArg?: I,
): [S, Dispatch<A>];
- 第一行定义了 useReducer 函数的默认导出。
- 第二行是 useReducer 函数的主体。它接收三个参数:reducer、initialState 和 initialArg。
- 第三行是 useReducer 函数的返回值。它返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是分发动作的函数。
function useReducer(reducer, initialState, initialArg) {
// 如果组件未挂载,创建并存储一个新状态对象
if (currentlyRenderingFiber === null) {
// 创建一个新状态对象
const initialStateForHooks =
typeof initialState === 'function' ? initialState() : initialState;
// 将新状态对象存储在组件的实例上
currentlyRenderingFiber.memoizedState = [initialStateForHooks, dispatchAction, reducer];
return [initialStateForHooks, dispatchAction];
} else {
// 获取组件的当前状态
const currentHook = currentlyRenderingFiber.memoizedState;
// 如果当前状态不存在,创建一个新的状态对象
if (currentHook !== null) {
// 获取当前状态
const currentState = currentHook[0];
// 返回当前状态和分发动作的函数
return [currentState, currentHook[1]];
} else {
// 创建一个新状态对象
const initialStateForHooks =
typeof initialState === 'function' ? initialState() : initialState;
// 将新状态对象存储在组件的实例上
currentlyRenderingFiber.memoizedState = [initialStateForHooks, dispatchAction, reducer];
// 返回新状态对象和分发动作的函数
return [initialStateForHooks, dispatchAction];
}
}
}
- 第 3 行到第 17 行是 useReducer 函数的主体。它首先检查当前组件是否已经挂载。如果组件还没有挂载,它将创建一个新的状态对象,并将其存储在组件的实例上。
- 第 19 行到第 27 行是 useReducer 函数的返回值。它返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是分发动作的函数。
const dispatchAction = (action: A) => {
// 获取当前状态
const currentState = currentlyRenderingFiber.memoizedState[0];
// 调用 reducer 函数,并用新的状态对象更新组件的实例
const nextState = reducer(currentState, action, initialArg);
currentlyRenderingFiber.memoizedState = [nextState, dispatchAction, reducer];
// 触发组件的重新渲染
scheduleWork(currentlyRenderingFiber, RendererContext.Sync);
};
- 第 3 行定义了分发动作的函数 dispatchAction。
- 第 5 行到第 7 行是 dispatchAction 函数的主体。它首先获取当前状态。
- 第 9 行调用 reducer 函数,并用新的状态对象更新组件