useMemo 和 useCallback 有什么区别?

useMemouseCallback 都是 React 的优化 Hooks,它们的主要目的是提高性能和避免不必要的计算或函数创建。那么它们又有哪些不同点呢?今天就让我们一起来讨论一下吧!

useMemo

useMemo 通过缓存计算结果来避免在每次渲染时都进行昂贵的计算。当你有一个计算或者对象构造函数,其结果依赖于某些输入(依赖项),你可以使用 useMemo 来存储这个结果。当依赖项没有改变时,React 将重用之前计算的结果,而不是重新计算。

工作原理:

  1. 在组件 render 期间,useMemo 接收一个创建函数和一个依赖项数组作为参数。
  2. 如果依赖项数组中的值与上一次 render 时的值相比没有任何变化,useMemo 就会返回上次计算的结果。
  3. 如果依赖项中有任何值发生了变化,useMemo 就会重新执行创建函数,并将新的结果缓存起来,然后返回这个新结果。

TypeScript 代码实例:

typescript 复制代码
import { useMemo } from 'react';

interface Props {
  array: number[];
}

function ComplexCalculation({ array }: Props) {
  // 使用 useMemo 缓存排序后的数组
  const sortedArray = useMemo(() => {
    // 这是一个复杂的计算或者对象构造函数
    return array.sort((a, b) => a - b);
  }, [array]);

  // 当 array 变化时,sortedArray 才会重新计算
  console.log('Sorted Array:', sortedArray);

  return (
    <div>
      {sortedArray.map(num => (
        <p key={num}>{num}</p>
      ))}
    </div>
  );
}

在这个例子中,我们有一个 ComplexCalculation 组件,它接收一个数字数组作为属性。我们使用 useMemo 来缓存排序后的数组。只有当 array 属性发生变化时,才会重新计算 sortedArray

这样做的好处是,如果 array 在多次渲染中保持不变,我们就无需每次都对数组进行排序,从而提高了性能。同时,由于 sortedArray 是在 useMemo 中计算的,所以它不会影响到 React 的渲染过程,保证了组件的状态管理。

useCallback

useCallback 主要用于缓存函数。当你有一个需要在 render 或者事件处理等场景中使用的函数,并且你希望在依赖项没有改变时保持该函数的引用不变,可以使用 useCallback

以下是一个详细的 useCallback 工作原理和 TypeScript 示例:

工作原理:

  1. 在组件 render 期间,useCallback 接收一个创建函数和一个依赖项数组作为参数。
  2. 如果依赖项数组中的值与上一次 render 时的值相比没有任何变化,useCallback 就会返回上次创建的函数引用。
  3. 如果依赖项中有任何值发生了变化,useCallback 就会重新执行创建函数,并将新的函数引用缓存起来,然后返回这个新引用。

TypeScript 代码实例:

typescript 复制代码
import { useCallback } from 'react';

interface Props {
  onButtonClick: (value: string) => void;
}

function ButtonHandler({ onButtonClick }: Props) {
  // 使用 useCallback 缓存 handleClick 函数引用
  const handleClick = useCallback(
    (event: React.MouseEvent<HTMLButtonElement>) => {
      onButtonClick('Clicked');
    },
    [onButtonClick],
  );

  // 当 onButtonClick 变化时,handleClick 函数引用才会重新创建
  console.log('Handle Click:', handleClick);

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

在这个例子中,我们有一个 ButtonHandler 组件,它接收一个回调函数 onButtonClick 作为属性。我们使用 useCallback 来缓存 handleClick 函数引用,该函数在按钮被点击时调用 onButtonClick

这样做的好处是,如果 onButtonClick 在多次渲染中保持不变,我们就无需每次都创建新的 handleClick 函数引用,从而降低了内存开销并提高了性能。同时,由于 handleClick 函数引用是稳定的,React 可以更高效地进行事件处理和比较,避免了不必要的子组件重新渲染。

拓展: 在某些情况下,你可能希望在 useCallback 中依赖其他状态或变量,但又不想触发函数引用的重新创建。这时,你可以使用 useRef 来存储这些变量,并将其添加到 useCallback 的依赖项数组中。因为 useRef 返回的对象引用始终不变,所以即使其 .current 属性发生变化,也不会触发 useCallback 中函数引用的重新创建。

以下是一个使用 useRef 拓展 useCallback 的 TypeScript 示例:

typescript 复制代码
import { useCallback, useRef } from 'react';

interface Props {
  onButtonClick: (value: string) => void;
}

function ButtonHandler({ onButtonClick }: Props) {
  const counterRef = useRef(0);
  
  // 使用 useCallback 缓存 handleClick 函数引用
  const handleClick = useCallback(
    (event: React.MouseEvent<HTMLButtonElement>) => {
      onButtonClick(`Clicked ${counterRef.current}`);
    },
    [onButtonClick], // 注意这里没有包含 counterRef
  );

  // 更新 counterRef 的 .current 属性,但不会触发 handleClick 函数引用的重新创建
  useEffect(() => {
    counterRef.current++;
  }, []);

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

在这个扩展的例子中,我们在 useCallback 中依赖了一个通过 useRef 创建的计数器 counterRef。尽管 counterRef.current 的值在每次渲染后都会增加,但由于我们只在 useEffect 中更新它,而没有将其添加到 useCallback 的依赖项数组中,因此 handleClick 函数引用不会因 counterRef 的变化而重新创建。这使得我们可以在保持函数引用稳定的同时,灵活地在函数内部访问和使用可变的状态或变量。


好了,今天的内容就到分享这里啦,很享受与大家一起学习,沟通交流问题,如果喜欢的话,请为我点个赞吧 !👍

作者:chenuvi

邮箱: chenui@outlook.com

plus: 最近在看工作机会,base 上海,有合适的前端岗位希望可以推荐一下啦!

相关推荐
dal118网工任子仪2 小时前
93,【1】buuctf web [网鼎杯 2020 朱雀组]phpweb
android·前端
赛博末影猫3 小时前
Spring理论知识(Ⅴ)——Spring Web模块
java·前端·spring
GISer_Jing4 小时前
DeepSeek 阐述 2025年前端发展趋势
前端·javascript·react.js·前端框架
prince_zxill4 小时前
RESTful 架构原则及其在 API 设计中的应用
前端·javascript·架构·前端框架·restful
禁默4 小时前
【学术投稿-2025年计算机视觉研究进展与应用国际学术会议 (ACVRA 2025)】从计算机基础到HTML开发:Web开发的第一步
前端·计算机视觉·html
Anlici6 小时前
强势DeepSeek——三种使用方式+推理询问指令😋
前端·人工智能·架构
Chaoran7 小时前
vue3 封装右键菜单组件
前端·javascript
海岸边的破木船7 小时前
为什么Vue3能更好的支持TS
前端
前端on9仔7 小时前
Chrome插件教程:一个小案例给掘金社区添加一键关灯效果
前端·chrome
小狸花子7 小时前
全平台异构文件上传架构设计:打破端侧限制的OSS上传实践
前端