什么是 useMemo
Hook?
useMemo
是一个 React Hook,用于缓存计算结果,避免在每次组件渲染时重复执行昂贵的计算。它通过记忆计算的值,只有在依赖项变化时才会重新计算,从而优化性能。
简单来说,useMemo
让你的计算结果“记住”上一次的值,减少不必要的计算开销。
为什么需要 useMemo
?
在 React 函数组件中,每次渲染都会重新执行组件函数体。如果有复杂或耗时的计算(比如过滤大数组、复杂数学运算),每次渲染都重复计算会影响性能。
useMemo
解决了这个问题,通过缓存计算结果,只有当依赖的变量变化时才重新计算。
适用场景:
- 复杂的计算逻辑(比如处理大数据、排序、过滤)
- 需要保持引用稳定的对象或数组(避免子组件不必要渲染)
- 优化性能,减少重复计算
useMemo
的基本语法
useMemo
接收两个参数:
- 计算函数:返回你想缓存的值(可以是任何类型:数字、对象、数组等)。
- 依赖数组:控制何时重新计算,只有依赖项变化时才重新运行计算函数。
基本结构:
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 和 filterreturn (<div><inputtype="number"value={filter}onChange={e => setFilter(Number(e.target.value))}/><ul>{filteredNumbers.map(num => (<li key={num}>{num}</li>))}</ul></div>);
}
运行效果:
filteredNumbers
是缓存的过滤和排序结果。- 只有当
numbers
或filter
变化时,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 操作)。- 有时
useMemo
和useCallback
一起用于优化useEffect
的依赖。
注意事项(新手常见坑)
-
不要滥用
useMemo
useMemo
本身有内存开销(缓存结果),只在计算昂贵或需要保持引用稳定时使用。- 如果计算很简单(比如
a + b
),直接计算比用useMemo
更高效。
-
依赖数组要正确填写
- 确保依赖数组包含计算函数中用到的所有变量,否则可能导致 bug。
- ESLint 的
react-hooks/exhaustive-deps
规则会帮你检查。
const result = useMemo(() => {return expensiveCalculation(count); // count 必须在依赖数组中 }, [count]);
-
引用稳定性不总是必要的
- 如果子组件没用
React.memo
,或计算结果不影响性能,可能不需要useMemo
。
- 如果子组件没用
-
不要用
useMemo
替代所有状态useMemo
是为优化计算设计的,不是状态管理工具。状态变化仍需用useState
或useReducer
。
总结
useMemo
的作用:缓存计算结果,避免重复执行昂贵计算,保持引用稳定。- 使用场景:
- 优化复杂计算(比如过滤、排序大数据)。
- 缓存对象/数组,配合
React.memo
避免子组件不必要渲染。
- 语法:
useMemo(fn, deps)
,返回缓存的值,依赖项变化时重新计算。 - 注意事项:只在需要时使用,正确声明依赖数组。