返回

从 useState 与 useReducer 源码入手,浅谈 Hooks 的运作原理

前端

如何通过剖析源码深入理解 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 的工作原理

  1. 首先,useState 会检查当前组件是否已经挂载。如果组件还没有挂载,它将创建一个新的状态对象,并将其存储在组件的实例上。
  2. 然后,useState 会返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是更新状态的函数。
  3. 当调用更新状态的函数时,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 的工作原理

  1. 首先,useReducer 会检查当前组件是否已经挂载。如果组件还没有挂载,它将创建一个新的状态对象,并将其存储在组件的实例上。
  2. 然后,useReducer 会返回一个数组,包含两个元素。第一个元素是当前状态,第二个元素是分发动作的函数。
  3. 当调用分发动作的函数时,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 函数,并用新的状态对象更新组件