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 上海,有合适的前端岗位希望可以推荐一下啦!

相关推荐
极客密码7 小时前
感谢雷总!Mimo大模型价值¥659/月的 MAX 套餐,让我免费领到了!
前端·ai编程·claude
深念Y8 小时前
我明白为什么B站没法在浏览器开直播了——Windows Chrome推流踩坑全记录
前端·chrome·webrtc·浏览器·srs·直播·flv
zhangxingchao8 小时前
AI应用开发七:可以替代 RAG 的技术
前端·人工智能·后端
Sun@happy8 小时前
现代 Web 前端渗透——基础篇(1)
前端·web安全
希冀1238 小时前
【CSS学习第十一篇】
前端·css·学习
隔窗听雨眠9 小时前
doctype、charset、meta如何控制整个渲染流水线
java·服务器·前端
kyriewen9 小时前
写组件文档写到吐?我用AI自动生成Storybook,同事以后直接抄
前端·javascript·面试
excel9 小时前
🧠 Prisma 表名大写 vs SQL 导出小写问题深度解析(附踩坑与解决方案)
前端·后端
周淳APP9 小时前
【前端工程化原理通识:从源头到运行时的理论阐述】
前端·编译·打包·前端工程化
五点六六六9 小时前
你敢信这是非Native页面写出来的渐变效果吗🌝(底层原理解析
前端·javascript·面试