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

0 阅读4分钟

一、核心概念

1. useMemo 到底是啥?

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

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

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

1. 基础格式

import { useMemo } from 'react';

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

2. 关键说明

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

三、什么时候用 useMemo?

1. 3 个核心场景

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

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

反例(不用用 useMemo):

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

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

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

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

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. 效果

image.png

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

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

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

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

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

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

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

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 反而会降低性能。

// 错误:简单计算用 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。
// 错误:用 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 的目标是 “优化性能”,如果用了之后没提升(甚至变慢),不如不用~