一、核心概念与适用场景
先简单理解这两个 Hook 的本质:
useMemo:缓存计算结果,避免组件每次渲染时重复执行昂贵的计算。useCallback:缓存函数引用,避免组件每次渲染时创建新的函数实例。(可视为useMemo的语法糖)
1. 什么时候用 useMemo?
当你的组件中有昂贵的计算逻辑(比如复杂的数组遍历、数据转换、数学运算),且这些计算的依赖项没有变化时,使用 useMemo 缓存结果:
jsx
import { useMemo } from 'react';
function ExpensiveComponent({ list }) {
// 场景1:昂贵的计算(比如过滤/排序大数组)
const filteredList = useMemo(() => {
// 假设 list 是长度上千的数组,过滤逻辑复杂
return list.filter(item => item.value > 100 && item.status === 'active');
}, [list]); // 仅当 list 变化时重新计算
// 场景2:避免传递给子组件的复杂数据重复创建(导致子组件不必要渲染)
const userInfo = useMemo(() => ({
name: '张三',
age: 20,
address: '北京'
}), []); // 依赖为空,仅初始化时创建一次
return <Child data={filteredList} user={userInfo} />;
}
2. 什么时候用 useCallback?
当你需要把函数传递给子组件(尤其是使用 React.memo 包裹的纯子组件),或函数作为 useEffect/useMemo 的依赖时,使用 useCallback 缓存函数引用:
jsx
import { useCallback, useEffect, useState } from 'react';
import Child from './Child'; // 假设 Child 用 React.memo 包裹
function Parent() {
const [count, setCount] = useState(0);
// 场景1:传递给 memo 子组件的回调函数
const handleClick = useCallback(() => {
console.log('子组件点击');
}, []); // 依赖为空,函数引用始终不变
// 场景2:作为 useEffect 的依赖
const fetchData = useCallback(() => {
console.log('请求数据,count:', count);
}, [count]); // 仅当 count 变化时更新函数引用
useEffect(() => {
fetchData();
}, [fetchData]); // 依赖缓存后的函数,避免不必要的执行
return <Child onClick={handleClick} />;
}
二、滥用的后果
useMemo 和 useCallback 并非“免费”,它们本身也有性能开销:
- 内存占用增加:缓存的计算结果/函数引用会一直存在于内存中,直到组件卸载或依赖项变化,大量不必要的缓存会占用更多内存。
- 初始化开销变大:每次渲染时,React 会先检查依赖项是否变化,再决定是否使用缓存,这个检查过程本身会消耗少量性能;如果是简单计算/简单函数,这个开销甚至会超过“重复执行/创建”的开销。
- 代码可读性降低:无意义地添加大量
useMemo/useCallback会让代码变得冗余、难以维护,掩盖真正需要优化的点。
反例(滥用):
jsx
// 错误:简单计算无需 useMemo
const sum = useMemo(() => 1 + 1, []);
// 错误:简单函数且不传递给子组件,无需 useCallback
const simpleFunc = useCallback(() => console.log('hello'), []);
三、判断是否需要使用的原则
- 先测后优化:先用 React DevTools 的 Profiler 工具定位性能瓶颈,确认是“重复计算”或“不必要的重渲染”导致的性能问题,再考虑使用。
- 聚焦“昂贵”操作:只有当计算/函数创建的成本 > 缓存的成本时,才值得使用。
- 子组件重渲染优化:仅当子组件用
React.memo/PureComponent包裹,且传递的 props 是函数/复杂对象时,才需要用useCallback/useMemo缓存 props。
总结
useMemo用于缓存昂贵计算的结果,useCallback用于缓存需要传递给子组件/作为依赖的函数引用。- 滥用会增加内存占用、渲染时的检查开销,还会降低代码可读性。
- 优化的核心原则是“先定位瓶颈,再针对性优化”,避免无意义的缓存。