react useMemo Hook详解

什么是 useMemo Hook?

useMemo 是一个 React Hook,用于缓存计算结果,避免在每次组件渲染时重复执行昂贵的计算。它通过记忆计算的值,只有在依赖项变化时才会重新计算,从而优化性能。

简单来说,useMemo 让你的计算结果“记住”上一次的值,减少不必要的计算开销。


为什么需要 useMemo

在 React 函数组件中,每次渲染都会重新执行组件函数体。如果有复杂或耗时的计算(比如过滤大数组、复杂数学运算),每次渲染都重复计算会影响性能。

useMemo 解决了这个问题,通过缓存计算结果,只有当依赖的变量变化时才重新计算。

适用场景

  • 复杂的计算逻辑(比如处理大数据、排序、过滤)
  • 需要保持引用稳定的对象或数组(避免子组件不必要渲染)
  • 优化性能,减少重复计算

useMemo 的基本语法

useMemo 接收两个参数:

  1. 计算函数:返回你想缓存的值(可以是任何类型:数字、对象、数组等)。
  2. 依赖数组:控制何时重新计算,只有依赖项变化时才重新运行计算函数。

基本结构:

import { useMemo } from 'react';

function MyComponent() {
  const memoizedValue = useMemo(() => {
    // 耗时计算逻辑
    return expensiveCalculation();
  }, [/* 依赖项 */]);

  return <div>{memoizedValue}</div>;
}
  • 返回值useMemo 返回缓存的计算结果(值),在依赖项不变的情况下,始终返回相同的引用。

useMemo 的使用场景

以下通过具体例子,带你理解 useMemo 的实际用途。

1. 优化昂贵计算

假设你有一个需要过滤和排序大数组的计算,useMemo 可以避免每次渲染都重复执行。

示例代码

import { useState, useMemo } from 'react';

function FilterList() {
  const [numbers, setNumbers] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  const [filter, setFilter] = useState(5);

  // 使用 useMemo 缓存过滤结果
  const filteredNumbers = useMemo(() => {
    console.log('执行过滤计算');
    return numbers.filter(num => num > filter).sort((a, b) => b - a);
  }, [numbers, filter]); // 依赖 numbers 和 filter

  return (
    <div>
      <input
        type="number"
        value={filter}
        onChange={e => setFilter(Number(e.target.value))}
      />
      <ul>
        {filteredNumbers.map(num => (
          <li key={num}>{num}</li>
        ))}
      </ul>
    </div>
  );
}

运行效果

  • filteredNumbers 是缓存的过滤和排序结果。
  • 只有当 numbersfilter 变化时,useMemo 才会重新计算(控制台打印“执行过滤计算”)。
  • 如果没有 useMemo,每次组件渲染(比如无关的状态变化)都会重新过滤和排序,浪费性能。

2. 保持对象或数组引用稳定(搭配 React.memo

如果一个对象或数组作为 props 传递给子组件,而子组件使用了 React.memo,每次渲染生成的新对象/数组会导致子组件重新渲染。useMemo 可以缓存对象/数组,保持引用稳定。

示例代码

import { useState, useMemo } from 'react';
import { memo } from 'react';

// 子组件使用 React.memo
const ChildComponent = memo(({ config }) => {
  console.log('ChildComponent 渲染了');
  return <div>配置: {config.name}</div>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);

  // 使用 useMemo 缓存对象
  const config = useMemo(() => {
    return { name: '我的配置' };
  }, []); // 空数组,config 永不重新生成

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>加1</button>
      <ChildComponent config={config} />
    </div>
  );
}

运行效果

  • 点击“加1”按钮,count 变化,ParentComponent 重新渲染。
  • ChildComponent 不会重新渲染,因为 config 的引用(由 useMemo 缓存)没变。
  • 如果不用 useMemo,每次渲染都会生成新对象 { name: '我的配置' },导致 ChildComponent 不必要地重新渲染。

useMemo vs. 其他 Hooks

  • useCallback 的区别

    • useMemo 缓存计算结果(可以是任何值:对象、数组、数字等)。
    • useCallback 缓存函数,返回函数引用。
    • useCallback(fn, deps) 等价于 useMemo(() => fn, deps)
    // useMemo 缓存对象
    const memoizedValue = useMemo(() => ({ name: '小明' }), []);
    
    // useCallback 缓存函数
    const memoizedFn = useCallback(() => console.log('点击'), []);
    
  • useEffect 的关系

    • useMemo 用于缓存计算结果,避免重复计算。
    • useEffect 用于处理副作用(比如 API 请求、DOM 操作)。
    • 有时 useMemouseCallback 一起用于优化 useEffect 的依赖。

注意事项(新手常见坑)

  1. 不要滥用 useMemo

    • useMemo 本身有内存开销(缓存结果),只在计算昂贵或需要保持引用稳定时使用。
    • 如果计算很简单(比如 a + b),直接计算比用 useMemo 更高效。
  2. 依赖数组要正确填写

    • 确保依赖数组包含计算函数中用到的所有变量,否则可能导致 bug。
    • ESLint 的 react-hooks/exhaustive-deps 规则会帮你检查。
    const result = useMemo(() => {
      return expensiveCalculation(count); // count 必须在依赖数组中
    }, [count]);
    
  3. 引用稳定性不总是必要的

    • 如果子组件没用 React.memo,或计算结果不影响性能,可能不需要 useMemo
  4. 不要用 useMemo 替代所有状态

    • useMemo 是为优化计算设计的,不是状态管理工具。状态变化仍需用 useStateuseReducer

总结

  • useMemo 的作用:缓存计算结果,避免重复执行昂贵计算,保持引用稳定。
  • 使用场景
    • 优化复杂计算(比如过滤、排序大数据)。
    • 缓存对象/数组,配合 React.memo 避免子组件不必要渲染。
  • 语法useMemo(fn, deps),返回缓存的值,依赖项变化时重新计算。
  • 注意事项:只在需要时使用,正确声明依赖数组。
posted @ 2025-09-29 16:45  牛奔  阅读(20)  评论(0)    收藏  举报