Skip to content

7.4 其他常用 Hooks:useContext, useReducer, useMemo, useCallback, useRef

除了 useStateuseEffect,React 还提供了一系列其他常用的 Hooks。它们都构建在我们在前面章节中讨论过的核心机制之上,为不同的场景提供了便捷的 API。

1. useReduceruseState 的基础

useReduceruseState 的一个更通用的版本,它适用于更复杂的状态逻辑。实际上,useState 内部就是通过 useReducer 实现的。

  • mountReducer: 在首次渲染时,它会创建 Hook,初始化 state,并创建 dispatch 函数。这与 mountState 非常相似,但它接收一个 reducer 函数作为参数。
  • updateReducer: 在更新时,它会处理 updateQueue 中的 actions,通过执行 reducer 函数来计算新的 state。
javascript
// In packages/react-reconciler/src/ReactFiberHooks.js

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = mountWorkInProgressHook();
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue: UpdateQueue<S, A> = {
    // ...
    lastRenderedReducer: reducer,
    // ...
  };
  hook.queue = queue;
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

2. useMemouseCallback:性能优化的利器

useMemouseCallback 是用于性能优化的 Hooks,它们通过“记忆化”(memoization)来避免不必要的计算和函数创建。

它们的实现与 useEffect 非常相似,都依赖于比较依赖项数组来决定是否要重新计算。

  • mountMemo / mountCallback: 在首次渲染时,它们会计算值(或存储函数),并将结果和依赖项都存储在 Hook 的 memoizedState 中。memoizedState 在这里是一个数组 [value, deps]
  • updateMemo / updateCallback: 在更新时,它们会取出上一次的依赖项,与新的依赖项进行比较(使用 areHookInputsEqual)。如果依赖项不变,则直接返回上一次缓存的值;如果变化,则重新计算值,并更新 memoizedState

useCallback(fn, deps) 实际上等同于 useMemo(() => fn, deps)

javascript
// In packages/react-reconciler/src/ReactFiberHooks.js

function mountMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

function updateMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps = prevState[1];
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

3. useRef:持有可变对象的引用

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。useRef 返回的 ref 对象在组件的整个生命周期内保持不变。

它的实现非常简单:

  • mountRef: 创建一个新的 Hook,并将其 memoizedState 初始化为一个 {current: initialValue} 对象。
  • updateRef: 直接返回上一次创建的 Hook 的 memoizedState

因为 updateRef 只是返回已存在的值,并且没有调用 dispatchAction 或标记 Fiber,所以更改 .current 属性不会触发组件的重新渲染。

javascript
// In packages/react-reconciler/src/ReactFiberHooks.js

function mountRef<T>(initialValue: T): {current: T} {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}

function updateRef<T>(initialValue: T): {current: T} {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

4. useContext:订阅 Context 的变化

useContext 的实现与其他 Hooks 有所不同。它不依赖于 mountWorkInProgressHookupdateWorkInProgressHook 来创建自己的 Hook 节点。相反,它直接调用 readContext

readContext 函数会:

  1. 从 Fiber 树中向上遍历,寻找最近的 Context.Provider
  2. 读取 Providervalue
  3. 将当前组件注册为该 Context 的消费者。当 Providervalue 发生变化时,React 会找到所有消费了该 Context 的 Fiber,并调度它们的更新。
javascript
// In packages/react-reconciler/src/ReactFiberHooks.js

function readContext<T>(context: ReactContext<T>): T {
  // ...
  const value = readContextForConsumer(currentlyRenderingFiber, context);
  // ...
  return value;
}

useContext 的魔力在于它将组件与上层 Provider 的更新联系起来,这是通过在 Fiber 树的 beginWork 阶段检查 context 变化来实现的,而不是通过 Hooks 自身的链表和更新队列。

至此,我们完成了对 React Hooks 核心源码的探索。从底层的链表设计,到 useState 的状态管理,再到 useEffect 的副作用处理,我们看到了一个高度协同、设计精巧的系统。正是这个系统,赋予了函数组件强大的能力和优雅的表达力。

Last updated: