比较useCallback、useMemo 和 React.memo
一、核心概念解析
API | 类型 | 作用 | 返回值 |
---|---|---|---|
useCallback | React Hook | 缓存函数引用 | 记忆化的函数 |
useMemo | React Hook | 缓存计算结果 | 记忆化的值 |
React.memo | 高阶组件(HOC) | 缓存组件渲染结果 | 优化后的组件 |
二、详细说明与代码示例
1、useCallback: 缓存函数引用
const memoizedFn = useCallback(() => {// 函数逻辑
}, [dependencies]);
- 解决的问题: 防止函数引用频繁变化导致子组件无效重渲染
- 典型场景:
- 函数作为 props 传递给优化过的子组件(React.memo)
- 函数作为其他 Hook 的依赖项(如useEffect)
// 父组件
function Parent() {const [count, setCount] = useState(0);// ✅ 缓存函数引用const increment = useCallback(() => setCount(c => c + 1), []);return <Child onIncrement={increment} />;
}// 子组件(使用 React.memo 优化)
const Child = React.memo(({ onIncrement }) => {console.log('子组件渲染');return <button onClick={onIncrement}>+</button>;
});
2、useMemo:缓存计算结果
const memoizedValue = useMemo(() => {// 复杂计算return computeExpensiveValue(a, b);
}, [a, b]);
- 解决的问题: 避免重复执行昂贵的计算
- 典型场景:
- 复杂计算(如数据转换、筛选)
- 保持对象/数组引用稳定(避免作为props传递时触发重渲染)
function Component({ items }) {// ✅ 缓存计算结果const filteredItems = useMemo(() => {return items.filter(item => item.value > 100);}, [items]);return <List items={filteredItems} />;
}
React.memo: 缓存组件渲染
const MemoizedComponent = React.memo(Component, arePropsEqual?);
- 解决的问题: 避免父组件更新导致子组件不必要的重渲染
- 工作方式:
- 对props进行浅比较(可自定义比较函数)
- props未变化时复用上次渲染结果
// 基础用法
const UserCard = React.memo(({ user }) => {return <div>{user.name}</div>;
});// 自定义比较函数
const UserCard = React.memo(({ user }) => {...},(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
三、三者区别对比
特性 | useCallback | useMemo | React.memo |
---|---|---|---|
优化目标 | 函数引用 | 计算结果 | 组件渲染 |
返回内容 | 函数 | 任何值 | 组件 |
触发条件 | 依赖变化时重建函数 | 依赖变化时重新计算 | props变化时重渲染 |
内存开销 | 缓存函数 | 缓存值 | 缓存虚拟DOM |
典型使用位置 | 组件内部 | 组件内部 | 组件导出时 |
性能影响 | 减少子组件重渲染 | 减少计算开销 | 减少DOM操作 |
四、协同工作示例
import React, {useState, useCallback, useMemo} from 'react';// 使用React.memo优化的子组件
const Chart = React.memo((data, onClick) => {console.log('渲染');return <svg onClick={onClick}>...</svg>;
})function Dashboard() {const [data, setData] = useState([]);const [filter, setFilter] = useState('month');// ✅ 缓存函数引用const handleClick = useCallback(() => {console.log('图表点击');}, []);//✅ 缓存计算结果const filteredData = useMemo(() => {return data.filter(d => d.period === filter);}, [data, filter]);return (<div><Chartdata={filteredData} // 稳定引用onClick={handleClick} // 稳定引用/></div>);
}
优化效果:
1、filter变化 → filteredData重新计算 → Chart重渲染
2、父组件状态更新 → handleClick引用不变 → Chart 不重渲染
3、点击图表 → 触发缓存的handleClick
五、使用误区与最佳实践
常见错误:
// ❌ 错误1:缺少依赖项
const badCallback = useCallback(() => {console.log(count); // 永远输出初始值
}, []); // 缺少count 依赖// ❌ 错误2:滥用useMemo
const simpleValue = useMemo(() => 42, []); // 直接使用 const 更高效// ❌ 错误3:期待 React.memo 深比较
React.memo(Comonent); // 默认只做浅比较
黄金法则:
1、按需优化: 先用常规写法,出现性能问题再优化
2、组合使用:
- React.memo + useCallback 优化组件树
- useMemo + useCallback 稳定复杂依赖
3、依赖诚实: 始终声明所有依赖项
4、避免深比较: 复杂对象考虑使用ID比对而非完整对象
六、性能影响分析
操作 | 内存开销 | CPU开销 | 适用场景 |
---|---|---|---|
使用useCallback | 缓存函数 | 依赖比较 | 函数传递/依赖项稳定化 |
使用useMemo | 缓存值 | 依赖比较 + 计算 | 昂贵计算/引用稳定化 |
使用React.memo | 缓存VDOM | Props比较 | 重渲染成本高的叶子组件 |
三者组合 | 较高 | 低 | 大型组件/频繁更新场景 |
不使用优化 | 无 | 高 | 简单组件/无性能瓶颈 |
经验总结: 在10%需要优化的关键路径上使用这些API,避免在简单组件中过度优化。