React useMemo 大白话用法文档(含注意项)

一、核心概念

1. useMemo 到底是啥?

大白话:useMemo 是 React 的 "计算结果缓存工具"------ 给它一个 "费时间的计算逻辑" 和 "依赖项列表",它会把计算结果存起来(缓存);只有依赖项变了,才重新计算;依赖项没变,直接返回缓存的结果,避免重复做无用功。

生活比喻:就像你算完 "全家月开销" 后,把总数写在纸上(缓存);只要没新增消费(依赖项没变),下次要用到时直接看纸就行,不用再把所有账单加一遍。

二、用法格式(3 步搞定)

1. 基础格式

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

// 缓存计算结果
const 缓存的结果 = useMemo(() => {
  // 第一步:写需要缓存的计算逻辑(比如大数组处理、复杂运算)
  return 复杂计算的结果;
}, [依赖项1, 依赖项2]); // 第二步:列依赖项(只有这些变了,才重新计算)

2. 关键说明

  • 第一个参数:无参回调函数(里面写计算逻辑),useMemo 会自动执行这个函数,缓存它的返回值;
  • 第二个参数:依赖项数组(必填,哪怕是空数组),控制是否重新计算;
  • 返回值:缓存的 "计算结果"(不是函数,是直接能用的值)。

三、什么时候用 useMemo?

1. 3 个核心场景

只有满足以下条件,用 useMemo 才有用,否则就是 "画蛇添足":

  1. 计算逻辑 "费性能":比如处理大数组(筛选 / 排序 / 求和)、循环次数多、复杂数学运算(不是 a + b 这种简单计算);
  2. 组件会 "频繁重新渲染":比如有计数器、输入框等频繁变化的状态,导致组件反复渲染;
  3. 计算的 "依赖项不常变":计算逻辑依赖的变量(比如数组、对象、数字)不会每次渲染都变。

反例(不用用 useMemo):

css 复制代码
// 错误:简单计算(a + b)用 useMemo,反而增加缓存开销 
const sum = useMemo(() => a + b, [a, b]);

四、核心用法案例(贴近实际开发)

1. 案例:表格数据筛选 + 排序(缓存处理结果)

场景:表格有 1 万条数据,用户可以输入关键词筛选、选择排序方式,组件还有一个无关计数器 ------ 只有筛选关键词 / 排序方式变了,才重新处理数据;计数器变了,直接用缓存的结果。

javascript 复制代码
import { useState, useMemo } from 'react';

export default function UseMemoTest() {
  // 1. 模拟1万条表格数据(组件外定义,避免重复创建)
  const allData = Array(10000).fill().map((_, i) => ({ id: i, name: `用户${i}`, age: i % 50 + 10 }));
  
  // 2. 状态(筛选关键词、排序方式、无关计数器)
  const [searchKey, setSearchKey] = useState(''); // 筛选依赖项
  const [sortType, setSortType] = useState('asc'); // 排序依赖项
  const [count, setCount] = useState(0); // 无关状态

  // 3. 用 useMemo 缓存"筛选+排序"结果(费性能的计算)
  const processedData = useMemo(() => {
    console.log('重新处理表格数据!'); // 观察触发时机
    // 步骤1:筛选(含关键词的用户)
    const filtered = allData.filter(item => 
      item.name.includes(searchKey)
    );
    // 步骤2:排序(升序/降序)
    const sorted = filtered.sort((a, b) => {
      return sortType === 'asc' ? a.age - b.age : b.age - a.age;
    });
    return sorted;
  }, [searchKey]); // 只依赖"筛选关键词"和"排序方式"

  return (
    <div>
      {/* 无关计数器(测试用) */}
      <button onClick={() => setCount(count + 1)}>计数器+1(不影响表格)</button>
      <p>计数器:{count}</p>

      {/* 筛选+排序控制 */}
      <input
        placeholder="输入用户名筛选"
        value={searchKey}
        onChange={(e) => setSearchKey(e.target.value)}
      />
      <button onClick={() => setSortType(sortType === 'asc' ? 'desc' : 'asc')}>
        切换排序(升/降)
      </button>

      {/* 表格渲染(用缓存的处理结果) */}
      <table>
        <thead><tr><th>ID</th><th>姓名</th><th>年龄</th></tr></thead>
        <tbody>
          {processedData.map(item => (
            <tr key={item.id}>
              <td>{item.id}</td><td>{item.name}</td><td>{item.age}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

2. 效果

  • 点 "计数器 + 1":组件重新渲染,但 searchKeysortType 没变,useMemo 直接用缓存,控制台不打印 "重新处理表格数据";
  • 输入筛选关键词 / 切换排序:依赖项变了,重新处理数据,控制台打印,表格更新。

五、关键注意项(避坑重点!)

1. 依赖项数组:不能漏、不能多、不能错

  • ❶ 不能漏写依赖:计算逻辑中用到的所有外部变量,都必须放进依赖数组,否则会拿到 "过时的缓存结果";
ini 复制代码
const processedData = useMemo(() => {
  return allData.filter(...).sort((a,b) => sortType === 'asc' ? a.age - b.age : b.age - a.age);
}, [searchKey]); // 漏了 sortType,切换排序时不会重新计算
  • ❷ 不能多写无关依赖:把不相关的变量放进依赖数组,会导致不必要的重新计算;
scss 复制代码
// 错误:count 是无关变量,放进依赖数组
const processedData = useMemo(() => {
  return allData.filter(...).sort(...);
}, [searchKey, sortType, count]); // 点计数器时也会重新处理数据
  • ❸ 空依赖数组:如果计算逻辑不依赖任何变量,用 [],表示 "只在组件首次渲染时计算一次,之后永远用缓存";
javascript 复制代码
const total = useMemo(() => { 
    return allData.reduce((sum, item) => sum + item.age, 0); 
    // 不依赖任何状态 
}, []); // 只计算一次

2. 引用类型依赖:必须保证 "引用不变"

数组、对象、函数是 "引用类型"------ 哪怕内容没变,只要重新创建(比如组件内定义的数组),引用就变了,useMemo 会误以为 "依赖变了",重新计算。

错误示例(引用类型导致无效缓存):

javascript 复制代码
function Table() {
  // 错误:组件每次渲染都会新建 allData 数组(引用变了)
  const allData = Array(10000).fill().map((_, i) => ({ id: i, name: `用户${i}` }));

  const processedData = useMemo(() => {
    console.log('重新处理数据!');
    return allData.filter(...);
  }, [allData]); // 每次渲染 allData 引用都变,导致频繁重新计算
}

正确处理方式

  • 方案 1:把引用类型放到组件外部(永久不变);
  • 方案 2:用 useMemouseRef 缓存引用类型;

3. 不要滥用:简单计算不用 useMemo

useMemo 本身也有 "缓存开销"(要存储结果、对比依赖项),如果计算逻辑很简单(比如 a + bstr.split(',')),用 useMemo 反而会降低性能。

ini 复制代码
// 错误:简单计算用 useMemo(没必要)
const sum = useMemo(() => a + b, [a, b]);
const arr = useMemo(() => str.split(','), [str]);

// 正确:直接计算
const sum = a + b;
const arr = str.split(',');

4. 只缓存 "计算结果",不缓存 "函数 / 副作用"

  • useMemo 是用来缓存 "纯计算结果" 的(没有副作用:不发请求、不修改 DOM、不更新状态);
  • 如果要缓存 "函数",用 useCallback(和 useMemo 类似,但专门缓存函数);
  • 如果要做 "副作用"(发请求、弹提示),用 useEffect,不要用 useMemo。
scss 复制代码
// 错误:用 useMemo 做副作用(发请求)
useMemo(() => {
  fetch('/api/data').then(res => res.json()); // 副作用,不应该用 useMemo
}, [searchKey]);

// 正确:副作用用 useEffect
useEffect(() => {
  fetch('/api/data').then(res => res.json());
}, [searchKey]);

5. React 版本注意:useMemo 不是 "铁打的缓存"

  • useMemo 的缓存是 "可选的"------React 在内存紧张时,可能会丢弃缓存,下次渲染重新计算(这种情况极少);

  • 不要依赖 useMemo 缓存 "必须唯一" 的值(比如唯一 ID、请求结果),如果需要强制缓存,用 useRef

六、常见错误对比(避坑指南)

错误用法 错误原因
useMemo((nums) => nums.reduce(...), [userNumbers]) 给回调函数加了参数,useMemo 不会传值,参数是 undefined
useMemo(() => { fetch('/api'); }, [searchKey]) 用 useMemo 做副作用(发请求)
useMemo(() => a + b, [a, b]) 简单计算滥用 useMemo
useMemo(() => { ... }, []) 但计算依赖外部变化的变量 漏写依赖项,导致拿到过时结果

七、总结(4 句话记牢)

  1. 核心功能:缓存 "费性能的计算结果",减少重复计算;
  2. 触发逻辑:只有 "依赖项变化" 才重新计算,无关状态变化不触发;
  3. 关键注意:依赖项要写全、引用类型要稳、不滥用、不做副作用;
  4. 等价关系:React useMemo ≈ Vue computed(都是缓存计算结果)。

记住:useMemo 的目标是 "优化性能",如果用了之后没提升(甚至变慢),不如不用~

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax