useMemo

要理解useMemo首先要明白React函数式组件是如何工作的

React 函数组件工作原理

  1. 渲染即调用:函数组件的本质就是一个 JavaScript 函数,每次状态(State)或属性(Props)发生变化时,React 就会调用这个函数来生成新的虚拟 DOM(JSX),并与之前的进行对比(Diff),最后将变化应用到真实 DOM 上。
  2. 每次渲染都是独立的:每一次函数调用(渲染),其内部的所有变量、函数、表达式都会重新声明和计算,这包括可能非常耗时的计算(例如,对一个包含成千上万条数据的列表进行过滤和排序)

性能浪费

我们知道函数渲染组件的工作原理后,有些场景会导致性能浪费。

举个例子

假设有一个计算量很大的函数 expensiveCalculation:

function MyComponent({ list, filter }) {
  // 每次渲染,无论 list 或 filter 是否改变,都会重新计算!
  const filteredList = expensiveCalculation(list, filter); 

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

如果list和filter没有变化,但父组件的某个其他状态(比如一个计数器count)发生了变化,导致MyComponent重新渲染,这个耗时的操作也会被毫无必要地再次执行,造成计算资源的浪费和潜在的卡顿。

为什么要使用useMemo?

useMemo的出现就是为了解决上述例子中的性能问题,核心目的就是优化

主要目的

主要目的是避免昂贵的重复计算,这是useMemo最根本、最直接的用途,说直白点就是保持引用相等,避免子组件不必要的渲染

它通过记忆化(Memoization)技术,只有在依赖发生变化时才会重新执行昂贵的计算函数。

function MyComponent({ list, filter }) {
  // 使用 useMemo:只有当 [list, filter] 改变时,才重新计算
  const filteredList = useMemo(() => {
    return expensiveCalculation(list, filter);
  }, [list, filter]); // 依赖项

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

现在即使父组件的count状态变了,导致MyComponent重新渲染,只要list和filter没变,useMemo就会直接返回上一次缓存的计算结果,完全跳过expensiveCalculation的执行,从而极大的提升性能。

保持引用相等,避免子组件不必要的渲染

这是一个非常重要且常见的应用场景,涉及到javascript的引用类型和React的渲染性能优化

  1. javascript的问题:每次组件渲染时,像 {}、[]、()=> {} 这样的字面量都会创建一个全新的对象/数组/函数,即使内容一模一样,它们在内存中的地址(引用)也是不同的。
  2. React的机制:当父组件渲染时,默认其所有子组件都会重新渲染。为了优化,我们常用React.memo()包裹子组件,让它对外部传入的Props进行浅比较。如果所有的Props的引用都没变,就跳过子组件的渲染。

没有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>
      {/* 即使用了 React.memo,Child 也会因为 config 引用不同而重新渲染 */}
      <Child config={config} />
    </div>
  );
}

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

点击按钮修改 countParent 重新渲染,创建了新的 config 对象。虽然 config 的内容没变,但它的引用变了,导致 React.memo 的浅比较失败,Child 组件被迫重新渲染。

使用useMemo的解决方案

function Parent() {
  const [count, setCount] = useState(0);
  // 使用 useMemo 缓存 config 对象,依赖项为空数组,意味着它只创建一次
  const config = useMemo(() => ({ theme: 'dark', size: 'large' }), []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      {/* 现在 config 的引用始终不变,Child 不会被不必要的渲染 */}
      <Child config={config} />
    </div>
  );
}

现在,无论 Parent 因为 count 重新渲染多少次,config 的引用始终保持不变,Child 组件因此避免了不必要的重渲染。

总结:为什么要使用 useMemo?

原因 描述 解决的问题
避免昂贵计算 缓存复杂计算的结果,仅在依赖变更时重新计算。 性能浪费:防止无关的渲染导致耗时操作重复执行。
保持引用稳定 缓存对象、数组等引用类型,保证其依赖不变时引用不变。 不必要的子组件渲染:与 React.memo 配合,避免因 props 引用变化导致的子组件重渲染。

核心思想:useMemo 是一种以空间换时间的优化策略(用一点内存来存储缓存结果,换取计算时间上的节省)。它不应该被滥用,而应针对性地应用于已存在或可预见的性能瓶颈上。

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