React中useMemo和useCallback的使用小结

 更新时间:2026年03月02日 08:27:25   作者:不会敲代码1  
本文主要介绍了深入理解React中useMemo和useCallback的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

在React函数组件中,每次状态更新都会导致整个组件函数重新执行。如果组件内部有复杂的计算或者传递了回调函数给子组件,可能会引发不必要的性能开销。React为我们提供了两个重要的Hook:useMemouseCallback,用于缓存计算结果和函数引用,从而优化组件性能。本文将从浅入深,结合实际代码,带你彻底理解这两个Hook的使用场景和原理。

1. 性能优化的必要性

我们先看一个简单的例子:

function App() {
  const [count, setCount] = useState(0);
  const [keyword, setKeyword] = useState('');

  const list = ['apple', 'banana', 'orange', 'pear'];

  // 每次渲染都会重新执行filter
  const filterList = list.filter(item => {
  // 测试fliter是否执行
  console.log("filter执行了")
  return item.includes(keyword) 
  });

  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>count: {count}</button>
      <ul>
        {filterList.map(item => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
}

这里 filterList 依赖于 keyword,但每次 count 改变导致组件重新渲染时,filterList 也会重新计算。如果 list 很大或者过滤逻辑复杂,这种不必要的计算就会影响性能。同样,如果我们将一个函数作为 prop 传递给子组件,每次父组件渲染都会生成一个新函数,导致子组件(即使使用 React.memo)也无法避免重新渲染。这正是 useMemouseCallback 要解决的问题。

效果图

20263282313081.gif (1718×988)

2. useMemo:缓存计算结果

useMemo 用于缓存一个计算后的值,只有当依赖项发生变化时,才会重新计算。

2.1 基本用法

const cachedValue = useMemo(computeFn, dependencies);
  • computeFn:纯函数,返回需要缓存的值。
  • dependencies:依赖项数组,当任意依赖项变化时,重新执行 computeFn

2.2 优化列表过滤

改进上面的例子:

import { useState, useMemo } from 'react';

export default function App() {
  const [count, setCount] = useState(0);
  const [keyword, setKeyword] = useState('');
  const list = ['apple', 'banana', 'orange', 'pear'];

  // 只有 keyword 变化时才重新过滤
  const filterList = useMemo(() => {
    console.log('filter执行了'); // 依赖变化时打印
    return list.filter(item => item.includes(keyword));
  }, [keyword]);

  return (
    <div>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>count: {count}</button>
      <ul>
        {filterList.map(item => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
}

现在,点击 count 按钮不会触发 filter 重新执行,只有输入框改变时才会。

效果图

20263282419971.gif (1772×938)

2.3 缓存昂贵计算

假设有一个非常耗时的函数 slowSum

function slowSum(n) {
  console.log('计算中...');
  let sum = 0;
  for (let i = 0; i < n * 1000000; i++) {
    sum += i;
  }
  return sum;
}

我们可以用 useMemo 将其结果缓存起来:

const [num, setNum] = useState(0);
const result = useMemo(() => slowSum(num), [num]);

这样只有当 num 变化时才会重新计算,其他状态变化不会触发 slowSum,这样做我们就可以节约成本。

2.4 小结

  • useMemo 适用于需要缓存计算结果的场景,比如过滤、排序、复杂数学运算等。
  • 依赖数组要正确填写,避免遗漏或多余依赖。
  • 不要滥用,如果计算本身不昂贵,就没有必要缓存。

3. useCallback:缓存函数引用

在React中,函数组件每次渲染都会重新创建内部定义的函数。如果这个函数作为 prop 传递给子组件,即使子组件使用了 React.memo,也会因为每次父组件传递的函数引用不同而导致子组件重新渲染。useCallback 就是用来缓存函数引用的。

3.1 基本用法

const cachedFn = useCallback(fn, dependencies);
  • fn:需要缓存的函数。
  • dependencies:依赖项数组,当依赖变化时,重新创建函数。

3.2 配合 React.memo 优化子组件

先看一个没有优化的例子:

import { useState, memo } from 'react';

const Child = memo(({ count, handleClick }) => {
  console.log('子组件渲染了');
  return <div onClick={handleClick}>子组件 count: {count}</div>;
});

export default function App() {
  const [count, setCount] = useState(0);
  const [num, setNum] = useState(0);

  // 每次 App 渲染,这里都会生成一个新函数
  const handleClick = () => {
    console.log('点击了');
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>count+1</button>
      <button onClick={() => setNum(num + 1)}>num+1</button>
      <Child count={count} handleClick={handleClick} />
    </div>
  );
}

虽然 Childmemo 包裹,理论上只有 counthandleClick 变化时才会重新渲染。但由于 handleClick 每次都是新函数,memo 浅比较发现 handleClick 引用变了,导致子组件也会重新渲染。即使点击的是 num 按钮,子组件依然会渲染。

效果图,可以看到当我们点击nums组件时,我们的子组件依然会执行

20263282503315.gif (1892×964)

使用 useCallback 缓存 handleClick

const handleClick = useCallback(() => {
  console.log('点击了');
}, []); // 空依赖表示函数永远不会重新创建

现在点击 num 按钮,handleClick 引用不变,子组件就不会重新渲染。

效果图,可以看到这个适合当我们点击nums组件,子组件就不会执行了

20263282552951.gif (1688×840)

3.3 依赖项的作用

如果回调函数内部使用了某些状态,需要将这些状态添加到依赖数组中,否则函数内部会一直使用旧的闭包值。

const handleClick = useCallback(() => {
  console.log('当前count:', count);
}, [count]); // 当 count 变化时,重新生成函数

这样既能保证函数引用在 count 不变时稳定,又能在 count 变化时获取最新值。

3.4 小结

  • useCallback 主要用于将回调函数传递给经过 memo 优化的子组件,避免不必要的重绘。
  • 如果回调函数不依赖任何组件状态,依赖数组可以为空。
  • 注意不要过度优化,如果子组件很轻量或者渲染成本很低,不一定需要使用 useCallback

4. useMemo 与 useCallback 的关系

  • useMemo 缓存的是useCallback 缓存的是函数
  • 实际上,useCallback(fn, deps) 等价于 useMemo(() => fn, deps)
  • 两者都是通过闭包和依赖追踪来实现缓存,目的都是减少不必要的计算或渲染。

5. 常见误区与注意事项

5.1 依赖数组要完整

无论是 useMemo 还是 useCallback,都要确保依赖数组中包含了所有在回调/计算中使用的响应式值(props、state、context等)。否则会因闭包捕获旧值而产生 bug。可以使用 eslint-plugin-react-hooks 自动检查依赖。

5.2 不要过早优化

只有在确实存在性能瓶颈时才使用这两个 Hook。滥用会增加代码复杂度,且缓存本身也有开销。可以通过 React DevTools 的 Profiler 来识别需要优化的组件。

5.3 缓存稳定但非“纯”的函数

如果 useCallback 的回调函数依赖于外部状态,但依赖数组为空,则函数内部的变量会一直保持初始值,可能导致 bug。一定要根据实际依赖填写数组。

6. 总结

  • useMemo 用于缓存计算值,避免每次渲染都重新执行昂贵计算。
  • useCallback 用于缓存函数引用,配合 React.memo 避免子组件不必要的重新渲染。
  • 两者都需要指定依赖项,确保缓存内容在依赖变化时更新。
  • 性能优化应当有的放矢,先测量后优化,不要盲目使用。

通过合理使用这两个 Hook,我们可以让 React 应用在复杂场景下依然保持流畅。希望本文能帮助你深入理解 useMemouseCallback,并在实际项目中正确应用它们。

到此这篇关于React中useMemo和useCallback的具体使用的文章就介绍到这了,更多相关React useMemo useCallback内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • React渲染的优化方案

    React渲染的优化方案

    react的渲染机制是非常独特的,有别于 Vue 框架的渲染次数的优化计算,React 很久以来就有PureComponent、shouldUpdate,本文小编给大家介绍了React渲染的优化方案,需要的朋友可以参考下
    2024-08-08
  • React.memo 实现原理解析

    React.memo 实现原理解析

    React.memo通过props浅比较或自定义函数,结合Fiber优化机制,跳过组件渲染及副作用,下面就来详细的介绍一下,感兴趣的可以了解一下
    2025-09-09
  • React Router 中实现嵌套路由和动态路由的示例

    React Router 中实现嵌套路由和动态路由的示例

    React Router 是一个非常强大和灵活的路由库,它为 React 应用程序提供了丰富的导航和 URL 管理功能,能够帮助我们构建复杂的单页应用和多页应用,这篇文章主要介绍了React Router 中如何实现嵌套路由和动态路由,需要的朋友可以参考下
    2023-05-05
  • 基于antd的autocomplete的二次封装查询示例

    基于antd的autocomplete的二次封装查询示例

    这篇文章主要为大家介绍了基于antd的autocomplete的二次封装查询示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • React二维数组的几种声明和使用过程

    React二维数组的几种声明和使用过程

    React二维数组声明需注意结构与值的可变性,正确方法包括直接赋值或动态初始化,避免使用const map=[[]]导致长度固定,动态扩展时应先定义一维数组再添加元素,确保数组大小可变
    2025-07-07
  • React事件处理和表单的绑定详解

    React事件处理和表单的绑定详解

    这篇文章主要介绍了React事件处理和表单的绑定,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • React进行路由跳转的方法汇总

    React进行路由跳转的方法汇总

    在 React 中进行路由跳转有多种方法,具体取决于你使用的路由库和版本,以下是常见的路由跳转方法汇总,主要基于 react-router-dom 库,文中并通过代码示例介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • 可定制React自动完成搜索组件Turnstone实现示例

    可定制React自动完成搜索组件Turnstone实现示例

    这篇文章主要为大家介绍了可定制React自动完成搜索组件Turnstone实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • React父组件数据实时更新了,子组件没有更新的问题

    React父组件数据实时更新了,子组件没有更新的问题

    这篇文章主要介绍了React父组件数据实时更新了,子组件没有更新的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • React处理高频的实时数据的解决方案

    React处理高频的实时数据的解决方案

    本文介绍了在开发一个实时日志查看器时遇到的React错误Maximum update depth exceeded(超过最大更新深度),并分析了问题的根源,解决了这个问题,需要的朋友可以参考下
    2025-11-11

最新评论