深入解析React Hooks:useCallback与useMemo的原理与区别

useCallback和useMemo

面试题:useCallback 和 useMemo 的区别是什么?

useCallback

useCallback 用法如下:

jsx 复制代码
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

使用 useCallback 最终会得到一个缓存的函数,该缓存函数会在 a 或者 b 依赖项发生变化时再更新。

mount 阶段

在 mount 阶段执行的就是 mountCallback,相关代码如下:

jsx 复制代码
function mountCallback(callback, deps) {
  // 首先还是创建一个 hook 对象
  const hook = mountWorkInProgressHook();
  // 依赖项
  const nextDeps = deps === undefined ? null : deps;
  // 把要缓存的函数和依赖数组存储到 hook 对象上
  hook.memoizedState = [callback, nextDeps];
  // 向外部返回缓存函数
  return callback;
}

在上面的代码中,首先会调用 mountWorkInProgressHook 得到一个 hook 对象,在 hook 对象的 memoizedState 上面保存 callback 以及依赖项目,最后向外部返回 callback

update阶段

update 调用的是 updateCallback,相关代码如下:

jsx 复制代码
function updateCallback(callback, deps) {
  // 拿到之前的 hook 对象
  const hook = updateWorkInProgressHook();
  // 新的依赖项
  const nextDeps = deps === undefined ? null : deps;
  // 之前的值,也就是 [callback, nextDeps]
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps = prevState[1]; // 拿到之前的依赖项
      // 对比依赖项是否相同
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 相同返回 callback
        return prevState[0];
      }
    }
  }
  // 否则重新缓存
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

在组件更新阶段,首先拿到之前的 hook 对象,从之前的 hook 对象的 memoizedState 上面拿到之前的依赖项,和新传入的依赖项做一个对比,如果相同,则返回之前缓存的 callback,否则就重新缓存,返回新的 callback

useMemo

用法如下:

jsx 复制代码
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

使用 useMemo 缓存的是一个值,这个值会在 a 或者 b 发生变化的时候重新进行计算并缓存。

mount 阶段

mount 阶段调用的是 mountMemo,代码如下:

jsx 复制代码
function mountMemo(nextCreate, deps) {
  // 创建 hook 对象
  const hook = mountWorkInProgressHook();
  // 存储依赖项
  const nextDeps = deps === undefined ? null : deps;

  // ... 
  
  // 执行传入的函数,拿到返回值
  const nextValue = nextCreate();
  // 将函数返回值和依赖存储到 memoizedState 上面
  hook.memoizedState = [nextValue, nextDeps];
  // 返回函数计算得到的值
  return nextValue;
}

在 mount 阶段首先会调用 mountWorkInProgressHook 方法得到一个 hook 对象,之后执行传入的函数(第一个参数)得到计算值,将计算值和依赖项目存储到 hook 对象的 memoizedState 上面,最后向外部返回计算得到的值。

update 阶段

update 阶段调用的是 updateMemo,相关代码如下:

jsx 复制代码
function updateMemo(nextCreate, deps) {
  // 获取之前的 hook 对象
  const hook = updateWorkInProgressHook();
  // 新的依赖项
  const nextDeps = deps === undefined ? null : deps;
  // 获取之前的 memoizedState,也就是 [nextValue, nextDeps]
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    // Assume these are defined. If they're not, areHookInputsEqual will warn.
    if (nextDeps !== null) {
      // 拿到之前的依赖项
      const prevDeps = prevState[1];
      // 比较和现在的依赖项是否相同
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 如果相同,则返回之前的值
        return prevState[0];
      }
    }
  }
  // ...
  // 否则重新计算
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

首先,仍然是从 updateWorkInProgressHook 上面拿到之前的 hook 对象,从而获取到之前的依赖项目,然后和新传入的依赖项目就行一个对比,如果依赖项目没有变化,则返回之前的计算值,否则就执行传入的函数重新进行计算,最后向外部返回新的计算值。

真题解答

题目:useCallback 和 useMemo 的区别是什么?

参考答案:

在 useCallback 内部,会将函数和依赖项一起缓存到 hook 对象的 memoizedState 属性上,在组件更新阶段,首先会拿到之前的 hook 对象,从之前的 hook 对象的 memoizedState 属性上获取到之前的依赖项目,对比依赖项目是否相同,如果相同返回之前的 callback,否则就重新缓存,然后返回新的 callback。

在 useMemo 内部,会将传入的函数执行并得到计算值,将计算值和依赖项目存储到 hook 对象的 memoizedState 上面,最后向外部返回计算得到的值。更新的时候首先是从 updateWorkInProgressHook 上拿到之前的 hook 对象,从而获取到之前的依赖值,和新传入的依赖进行一个对比,如果相同,就返回上一次的计算值,否则就重新调用传入的函数得到新的计算值并缓存,最后向外部返回新的计算值。

接下来,让我们用小朋友能听懂的方式来解释 useCallbackuseMemo,再结合 React 源码里的"小秘密"来理解它们是怎么工作的!🎮


1. 什么是 useCallbackuseMemo

它们就像 "记忆魔法",帮助 React 记住一些东西,避免重复计算或重新创建,让电脑跑得更快!🚀

  • useCallback :记住一个 函数 (比如 () => console.log("Hi!")),只有当它的"依赖项"(比如 [a, b])变了,才重新创建这个函数。
  • useMemo :记住一个 计算出来的值 (比如 1 + 2 = 3),只有当依赖项变了,才重新计算。

2. React 源码里的"小秘密"🔍

React 用了一个叫 hook.memoizedState 的"魔法盒子"来记住这些东西!📦

(1) useCallback 是怎么工作的?
  • 第一次渲染(Mount)

    js 复制代码
    function mountCallback(callback, deps) {
      const hook = mountWorkInProgressHook(); // 创建一个"魔法盒子"
      hook.memoizedState = [callback, deps];  // 把函数和依赖项存进去
      return callback; // 直接返回这个函数
    }
    • 就像你第一次写作业,把答案(函数)和题目(依赖项)一起记在本子上📝。
  • 更新时(Update)

    js 复制代码
    function updateCallback(callback, deps) {
      const hook = updateWorkInProgressHook(); // 找到之前的"魔法盒子"
      const prevDeps = hook.memoizedState[1]; // 看看上次的依赖项
      if (areHookInputsEqual(deps, prevDeps)) { // 如果依赖项没变
        return hook.memoizedState[0]; // 直接返回上次的函数!
      }
      // 如果变了,就存新的函数和依赖项
      hook.memoizedState = [callback, deps];
      return callback;
    }
    • 就像检查作业题目有没有变,如果没变,直接抄上次的答案!📖
(2) useMemo 是怎么工作的?
  • 第一次渲染(Mount)

    js 复制代码
    function mountMemo(nextCreate, deps) {
      const hook = mountWorkInProgressHook(); // 创建"魔法盒子"
      const nextValue = nextCreate(); // 先计算一次(比如 1+2=3)
      hook.memoizedState = [nextValue, deps]; // 存结果和依赖项
      return nextValue; // 返回计算的值
    }
    • 就像你第一次算 1 + 2,把答案 3 记在本子上。
  • 更新时(Update)

    js 复制代码
    function updateMemo(nextCreate, deps) {
      const hook = updateWorkInProgressHook(); // 找到之前的"魔法盒子"
      const prevDeps = hook.memoizedState[1]; // 上次的依赖项
      if (areHookInputsEqual(deps, prevDeps)) { // 如果依赖项没变
        return hook.memoizedState[0]; // 直接返回上次的答案!
      }
      // 如果变了,重新计算并存新的结果
      const nextValue = nextCreate();
      hook.memoizedState = [nextValue, deps];
      return nextValue;
    }
    • 就像检查数学题有没有变,如果还是 1 + 2,直接写 3,不用再算一遍!🧮

3. 它们有什么区别?🤔

useCallback useMemo
记住什么 函数 计算的值
例子 () => {} 1 + 2
React 存什么 [函数, deps] [值, deps]
  • useCallback :避免函数被重新创建(比如传给子组件的 onClick)。
  • useMemo :避免重复计算(比如算 sumfilter 列表)。

4. 总结🎯

  • React 用 hook.memoizedState 这个"魔法盒子"记住函数或值。
  • 如果依赖项没变,就用上次的!(就像作业题目没变,直接抄答案✏️)
  • 如果变了,就重新算/创建!(就像题目变了,得重新写作业📚)

现在你知道 React 是怎么"偷懒"让电脑跑得更快了吧!😉

相关推荐
前端 贾公子5 小时前
pnpm 的 resolution-mode 配置 ( pnpm 的版本解析)
前端
伍哥的传说6 小时前
React 自定义Hook——页面或元素滚动到底部监听 Hook
前端·react.js·前端框架
麦兜*7 小时前
Spring Boot 集成Reactive Web 性能优化全栈技术方案,包含底层原理、压测方法论、参数调优
java·前端·spring boot·spring·spring cloud·性能优化·maven
知了一笑8 小时前
独立开发第二周:构建、执行、规划
java·前端·后端
UI前端开发工作室8 小时前
数字孪生技术为UI前端提供新视角:产品性能的实时模拟与预测
大数据·前端
Sapphire~8 小时前
重学前端004 --- html 表单
前端·html
遇到困难睡大觉哈哈9 小时前
CSS中的Element语法
前端·css
Real_man9 小时前
新物种与新法则:AI重塑开发与产品未来
前端·后端·面试
小彭努力中9 小时前
147.在 Vue3 中使用 OpenLayers 地图上 ECharts 模拟飞机循环飞行
前端·javascript·vue.js·ecmascript·echarts
老马聊技术9 小时前
日历插件-FullCalendar的详细使用
前端·javascript