useMemo 和 useCallback 有什么区别?

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

相关推荐
阡陌数智30 分钟前
大模型推理抖动深度剖析:生产环境下时延波动根因定位与根治方案
人工智能·语言模型·性能优化·推荐算法
前端snow36 分钟前
ai agent --- DeepAgents 中间件
前端
data analyse 4561 小时前
报表能自定义的国内统计工具有哪些
前端·数据分析
Zguigo1 小时前
【CUDA4】GPU里面的数据放到哪里?为什么有的数据访问快,有的慢?
性能优化
IT_陈寒1 小时前
JavaScript闭包的这个坑,我居然今天才爬出来
前端·人工智能·后端
可乐鸡翅yeah_1 小时前
hls.js 切换多个视频源,新手开发常见踩坑
开发语言·前端·javascript·ios·ffmpeg·音视频·safari
葡萄城技术团队1 小时前
复制走的是数字,留下的是身份:Web 表格里被忽视的数据断层
前端
kyriewen2 小时前
我实测了前端日期的 5 个坑:差 8 小时只是开始
前端·javascript·程序员
测试运维日常笔记2 小时前
大模型应用专项测试指南:从 Prompt、RAG 到 Agent 的完整链路
前端·prompt