useMemo

React useMemo 深度解析

一、从函数组件工作原理说起

1.1 渲染即调用

函数组件的本质就是一个 JavaScript 函数。每次 State 或 Props 发生变化时,React 就会调用这个函数

  1. 执行函数体,生成新的虚拟 DOM(JSX)
  2. 与上一次的虚拟 DOM 进行 Diff 对比
  3. 将变化应用到真实 DOM

1.2 每次渲染都是独立的

每一次函数调用(渲染),其内部的所有变量、函数、表达式都会重新声明和计算

这意味着——如果一个计算非常耗时(比如对十万条数据做过滤排序),即使触发重新渲染的只是一个无关的 state 变化,这个耗时计算也会被毫无意义地重复执行


二、useMemo 是什么

useMemo 是一个 React Hook,用于缓存计算结果

const cachedValue = useMemo(calculateValue, dependencies)
  • calculateValue:一个"纯函数",返回你希望缓存的值
  • dependencies:依赖项数组,只有依赖发生变化时才重新执行 calculateValue
  • 返回值:缓存的计算结果

useMemo 不改变组件的行为,只改变计算的 时机——从"每次都算"变成"依赖变了才算"。


三、核心用途一:避免昂贵的重复计算

没有 useMemo

function MyComponent({ list, filter }) {
  // 每次渲染都重新计算,即使 list 和 filter 没变
  const filteredList = expensiveCalculation(list, filter);

  return <div>{filteredList.map(item => ...)}</div>;
}

如果父组件另一个状态(比如 count)变了,MyComponent 重新渲染,expensiveCalculation 会被白白执行一次

使用 useMemo

function MyComponent({ list, filter }) {
  const filteredList = useMemo(
    () => expensiveCalculation(list, filter),
    [list, filter] // 只有这两个变了才重算
  );

  return <div>{filteredList.map(item => ...)}</div>;
}

现在,即使父组件 count 变了导致本组件重新渲染,只要 listfilter 引用没变,useMemo 直接返回上一次缓存的结果,跳过整个计算过程

适用场景

场景 是否推荐 useMemo
万级以上数据过滤/排序 ✅ 强烈推荐
复杂 JSON 数据转换 ✅ 强烈推荐
正则匹配/字符串解析 ✅ 推荐
简单加减乘除 ❌ 不推荐(useMemo 本身有开销)

四、核心用途二:保持引用稳定,配合 React.memo

4.1 问题的根源

JavaScript 的引用类型规则:每次渲染时,{}[]() => {} 这样的字面量都会创建一个全新的对象,即使内容一模一样,内存地址也不同。

{} === {}  // false,每次都是新对象

4.2 没有 useMemo 的连锁反应

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

  // 每次 Parent 渲染都创建一个全新的 config 对象
  const config = { theme: 'dark', size: 'large' };

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child config={config} />
    </div>
  );
}

const Child = React.memo(({ config }) => {
  console.log('Child rendered!'); // 每次点按钮都会打印!
  return <div>Theme: {config.theme}</div>;
});

流程:

点击按钮 → count 变 → Parent 重新渲染
                        → 创建新 config(引用变了)
                        → React.memo 浅比较 config → 引用不同
                        → Child 重新渲染(浪费)

4.3 用 useMemo 修复

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

  // config 引用被缓存,永远不变
  const config = useMemo(() => ({ theme: 'dark', size: 'large' }), []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <Child config={config} />
    </div>
  );
}

现在 config 只创建一次,引用永远不变,Child 不会再被无端重渲染。

适用场景

场景 是否推荐 useMemo
传给 React.memo 子组件的对象/数组/函数 ✅ 强烈推荐
作为 useEffect 依赖的引用类型值 ✅ 推荐(避免死循环)
组件内临时用的普通对象 ❌ 不推荐(没传给子组件就没必要)

五、本质:以空间换时间

比喻 代价
不用 useMemo 每次现买现做 花时间(重复计算)
使用 useMemo 做完存抽屉,下次直接拿 花空间(缓存占内存)

useMemo 的核心思想是:用一点点内存,换取计算时间上的大幅节省。


六、什么时候不该用 useMemo

useMemo 不是银弹,滥用反而可能拖慢性能:

// ❌ 画蛇添足:简单的计算,useMemo 的开销比计算本身还大
const total = useMemo(() => a + b, [a, b]);

// ❌ 画蛇添足:JSX 中直接用基础类型,不需要缓存
return <div>{count}</div>;

useMemo 的代价:每次渲染时,React 都要比较依赖项数组(浅比较),还要维护缓存池。如果计算本身不昂贵,这个开销 > 直接计算的开销。

判断标准:先有可感知的性能问题,再用 useMemo。不要为了用而用。


七、总结

维度 不用 useMemo 用 useMemo
昂贵计算 每次渲染重复算,浪费 CPU 依赖不变则跳过,性能提升
引用稳定性 每次渲染新建对象,React.memo 防不住 引用缓存不变,子组件跳过渲染
内存 不占额外内存 多占一点缓存内存
使用原则 适合简单计算、非 props 传递的场景 精准针对已存在的性能瓶颈

一句话

先有性能问题,再用 useMemo。它的意义不是"让代码变快",而是"让相同输入不必重复计算"。

posted @ 2025-09-01 00:22  HuangBingQuan  阅读(54)  评论(0)    收藏  举报