回答 usememo 和 usecallback

63 阅读3分钟

useMemo 和 useCallback 是 React 面试中非常常见的一对“兄弟 Hook”, 很多面试官会问:

“useMemo 和 useCallback 有什么区别?” “为什么要用它们?底层实现原理是什么?” “它们在 Fiber 里是怎么保存和判断依赖变化的?”

下面我帮你分成三个层次来讲解,从面试高分回答到源码级理解 👇

🧩 一、面试简答版(2 分钟高分回答)

useMemo 和 useCallback 都是 React 提供的性能优化 Hook, 用来 缓存函数组件每次渲染时的计算结果或函数引用,防止不必要的重新渲染。

Hook 作用 返回值 使用场景 useMemo(fn, deps) 缓存计算结果 返回计算结果 计算开销大(如复杂计算、过滤、排序) useCallback(fn, deps) 缓存函数引用 返回函数本身 传递给子组件的回调函数,避免子组件重复渲染

一句话区分:

✅ useMemo 返回值是一个计算结果, ✅ useCallback 返回值是一个函数。

它们内部原理几乎一样,只是 useCallback(fn, deps) 等价于 useMemo(() => fn, deps)。

💡 二、原理理解(中高级面试)

1️⃣ 内部实现原理概述

在源码层面(ReactFiberHooks.js): • 每次组件渲染时,都会执行函数组件,从而执行到 useMemo 或 useCallback • React 内部会通过 updateMemo 或 updateCallback 来判断依赖是否变化 • 比较依赖时使用 areHookInputsEqual(nextDeps, prevDeps) • 如果依赖未变,直接复用上次缓存的值 • 如果依赖变了,就重新计算 fn(),并缓存新的结果和依赖

2️⃣ 源码级伪实现(简化理解)

🔹 useMemo

function useMemo(create, deps) { const hook = updateWorkInProgressHook(); // 获取当前 hook 节点 const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState;

// 比较依赖 if (prevState !== null && nextDeps !== null) { const [prevValue, prevDeps] = prevState; if (areHookInputsEqual(nextDeps, prevDeps)) { // ✅ 依赖未变,直接复用旧值 return prevValue; } }

// 依赖变了或初次渲染 → 重新计算 const nextValue = create(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; }

🔹 useCallback

其实就是简化版的 useMemo:

function useCallback(callback, deps) { return useMemo(() => callback, deps); }

👉 所以 useCallback(fn, deps) 只是让 useMemo 返回函数本身而已。

3️⃣ React 内部怎么存储这些值?

在 Fiber 架构中,每个函数组件都有一个 hook 链表(通过 memoizedState 串联)。

结构大致如下:

fiber.memoizedState (Hook链表) ├─ useState → { memoizedState: currentState } ├─ useMemo → { memoizedState: [cachedValue, deps] } └─ useCallback → { memoizedState: [callbackFn, deps] }

React 每次渲染时会: • 从旧 fiber 的 hook 链表里取出上一次的 hook(prevHook) • 进行依赖比较 • 决定是否复用 memoizedState

这就是为什么 Hook 调用顺序不能变(否则链表错位)。

⚙️ 三、使用示例与区别

🔸 useMemo 示例

const expensiveValue = useMemo(() => { return heavyCalculation(a, b); }, [a, b]);

👉 当 a 或 b 不变时,不会重新计算,返回上次缓存的结果。 适用于: • 计算量大 • 子组件依赖计算结果作为 props

🔸 useCallback 示例

const handleClick = useCallback(() => { setCount(c => c + 1); }, []);

// 传给子组件 return ;

👉 确保每次渲染时 handleClick 的函数引用相同, 防止子组件因为 props 变化而重新渲染。

🔍 四、依赖比较机制

React 使用一个函数 areHookInputsEqual 来比较依赖数组:

function areHookInputsEqual(nextDeps, prevDeps) { if (prevDeps === null || nextDeps.length !== prevDeps.length) return false; for (let i = 0; i < nextDeps.length; i++) { if (!Object.is(nextDeps[i], prevDeps[i])) return false; } return true; }

•	使用 Object.is(比 === 更精确)
•	一旦某个依赖项不相等,就认为依赖变了

🧠 五、面试加分点

你可以补充一句(很多人不会说这点):

在 React 18 之后,useMemo 和 useCallback 的缓存是基于 Fiber 的 hook 链表存储的, 它们不会跨渲染持久化到内存之外; 一旦组件卸载或重新挂载,缓存全部失效。

所以 useMemo / useCallback 并非通用缓存工具,而是用于避免渲染期重复计算和引用变化的优化手段。

✅ 面试总结回答模板(背这段即可):

useMemo 和 useCallback 都是 React 的性能优化 Hook, 前者缓存计算结果,后者缓存函数引用。 它们内部通过 Fiber 的 hook 链表存储上次的值与依赖数组, 在下一次 render 阶段时比较新旧依赖(areHookInputsEqual)。 如果依赖未变化,直接复用上一次的结果;否则重新计算并缓存。

useCallback(fn, deps) 本质等价于 useMemo(() => fn, deps)。