Appearance
7.4 其他常用 Hooks:useContext, useReducer, useMemo, useCallback, useRef
除了 useState 和 useEffect,React 还提供了一系列其他常用的 Hooks。它们都构建在我们在前面章节中讨论过的核心机制之上,为不同的场景提供了便捷的 API。
1. useReducer:useState 的基础
useReducer 是 useState 的一个更通用的版本,它适用于更复杂的状态逻辑。实际上,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. useMemo 和 useCallback:性能优化的利器
useMemo 和 useCallback 是用于性能优化的 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 有所不同。它不依赖于 mountWorkInProgressHook 或 updateWorkInProgressHook 来创建自己的 Hook 节点。相反,它直接调用 readContext。
readContext 函数会:
- 从 Fiber 树中向上遍历,寻找最近的
Context.Provider。 - 读取
Provider的value。 - 将当前组件注册为该
Context的消费者。当Provider的value发生变化时,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 的副作用处理,我们看到了一个高度协同、设计精巧的系统。正是这个系统,赋予了函数组件强大的能力和优雅的表达力。