React 中的 useMemo 与 useCallback:性能优化的利器
在 React 开发中,随着组件复杂度提升,性能优化逐渐成为不可忽视的问题。当组件状态更新时,函数组件会重新执行,这可能导致不必要的计算或子组件重渲染。React 提供的useMemo和useCallback两个 Hooks,正是解决这类性能问题的关键工具。本文将结合实际代码,详细讲解它们的作用、用法及应用场景。
为什么需要性能优化?
在 React 中,当组件的状态(useState)或上下文(useContext)发生变化时,组件会重新执行其函数体。这一机制确保了 UI 与数据的同步,但也可能带来副作用:
- 组件内的计算逻辑会被重复执行(即使计算结果不变)
- 传递给子组件的 props (尤其是函数)会被重新创建,导致子组件不必要的重渲染
例如,在未优化的情况下,当一个与计算逻辑无关的状态变化时,过滤列表或复杂计算会被重复执行;父组件传递的回调函数每次都会是新的引用,导致使用memo包装的子组件依然重渲染。 useMemo和useCallback正是为解决这些问题而生。
useMemo:缓存计算结果
作用
useMemo用于缓存计算结果,避免组件重渲染时重复执行昂贵的计算逻辑。只有当依赖项发生变化时,才会重新计算结果。
用法
javascript
const 缓存的结果 = useMemo(() => {
// 计算逻辑(可能是昂贵的操作)
return 计算结果;
}, [依赖项数组]); // 依赖项变化时,重新执行计算
- 第一个参数:一个函数,封装需要缓存结果的计算逻辑
- 第二个参数:依赖项数组,只有数组中的值发生变化时,才会重新执行第一个函数
代码示例
- 过滤列表优化
ini
const [count,setCount] = useState(0);
const list = ['apple','banana','orange','pear'];
const [keyword,setKeyword] = useState('');
// 用useMemo缓存过滤结果,仅当keyword变化时重新过滤
const filterList = useMemo(() => {
return list.filter(item => item.includes(keyword))
}, [keyword])
return (
<input type="text" value={keyword} onChange={e => setKeyword(e.target.value)}/>
{
filterList.map(item => (
<li key={item}>{item}</li>
))
}
<button onClick={() => setCount(count + 1)}>count + 1</button>
)
若不使用useMemo,每次组件重渲染(即使count变化,与过滤逻辑无关),filter都会重新执行。使用useMemo后,只有keyword变化时才会重新过滤,减少了不必要的计算。
- 昂贵计算优化
ini
// 模拟一个昂贵的计算(循环100万次)
function slowSum(n) {
console.log('计算中...');
let sum = 0;
for(let i=0;i<n*1000000;i++){
sum+=i;
}
return sum;
}
// 用useMemo缓存计算结果,仅当num变化时重新计算
const result = useMemo(() => {
return slowSum(num);
}, [num])
slowSum是一个耗时计算,若不缓存,每次组件重渲染(如count或keyword变化)都会触发它。使用useMemo后,只有num变化时才会重新执行,显著提升性能。
应用场景
- 包含昂贵计算的逻辑(如大量循环、复杂数据处理)
- 需要作为
props传递的计算属性(避免子组件因值频繁变化而重渲染) - 依赖多个状态 / 变量的推导值(确保仅在依赖变化时更新)
useCallback:缓存回调函数
作用
useCallback用于缓存函数引用 ,避免组件重渲染时重复创建相同逻辑的函数。常与memo配合使用,防止子组件因接收的函数props变化而不必要地重渲染。
用法
scss
const 缓存的函数 = useCallback(() => {
// 函数逻辑
}, [依赖项数组]); // 依赖项变化时,重新创建函数
- 第一个参数:需要缓存的回调函数
- 第二个参数:依赖项数组,只有数组中的值发生变化时,才会重新创建函数
代码示例
- 子组件优化
javascript
// 用memo包装子组件,使其仅在props真正变化时重渲染
const Child = memo(({count,handleClick}) => {
console.log('child 重新渲染');
return (
<div onClick={handleClick}>
子组件{count}
</div>
)
})
- 缓存回调函数
javascript
// 用useCallback缓存handleClick,仅当count变化时重新创建
const handleClick = useCallback(() => {
console.log('click');
}, [count])
若不使用useCallback,每次父组件重渲染,handleClick都会是新的函数引用。即使memo包装了Child,由于handleClick变化,子组件依然会重渲染。使用useCallback后,只有count变化时handleClick才更新,因此当num变化时,子组件不会重新渲染。
应用场景
- 传递给子组件的回调函数 (如
onClick、onChange) - 作为依赖项传入
useEffect等 Hooks 的函数(避免useEffect不必要地触发) - 配合
memo、useMemo等优化手段,确保子组件仅在必要时重渲染
总结
useMemo和useCallback都是 React 性能优化的核心工具,它们的本质是缓存:
useMemo缓存计算结果,解决 "重复计算" 问题useCallback缓存函数引用,解决 "子组件不必要重渲染" 问题
使用时需注意:
- 不要过度优化:简单计算或不频繁重渲染的组件无需使用,缓存本身也有开销
- 依赖项数组必须正确:遗漏依赖会导致缓存结果 / 函数过时,引发逻辑错误
- 结合场景使用:昂贵计算用
useMemo,回调函数用useCallback配合memo
合理使用这两个 Hooks,能有效提升 React 应用的性能,尤其在复杂组件或高频更新场景中效果显著。