比较useCallback、useMemo 和 React.memo

一、核心概念解析

API 类型 作用 返回值
useCallback React Hook 缓存函数引用 记忆化的函数
useMemo React Hook 缓存计算结果 记忆化的值
React.memo 高阶组件(HOC) 缓存组件渲染结果 优化后的组件

二、详细说明与代码示例

1、useCallback: 缓存函数引用

javascript 复制代码
const memoizedFn = useCallback(() => {
	// 函数逻辑
}, [dependencies]);
  • 解决的问题: 防止函数引用频繁变化导致子组件无效重渲染
  • 典型场景:
    • 函数作为 props 传递给优化过的子组件(React.memo
    • 函数作为其他 Hook 的依赖项(如useEffect
javascript 复制代码
// 父组件
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:缓存计算结果

javascript 复制代码
const memoizedValue = useMemo(() => {
	// 复杂计算
	return computeExpensiveValue(a, b);
}, [a, b]);
  • 解决的问题: 避免重复执行昂贵的计算
  • 典型场景:
    • 复杂计算(如数据转换、筛选)
    • 保持对象/数组引用稳定(避免作为props传递时触发重渲染)
javascript 复制代码
function Component({ items }) {
	// ✅ 缓存计算结果
	const filteredItems = useMemo(() => {
		return items.filter(item => item.value > 100);
	}, [items]);

	return <List items={filteredItems} />;
}

React.memo: 缓存组件渲染

javascript 复制代码
const MemoizedComponent = React.memo(Component, arePropsEqual?);
  • 解决的问题: 避免父组件更新导致子组件不必要的重渲染
  • 工作方式:
    • 对props进行浅比较(可自定义比较函数)
    • props未变化时复用上次渲染结果
javascript 复制代码
// 基础用法
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操作

四、协同工作示例

javascript 复制代码
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>
			<Chart
				data={filteredData}  // 稳定引用
				onClick={handleClick}  // 稳定引用
			/>
		</div>
	);
}

优化效果:

1、filter 变化 → filteredData 重新计算 → Chart 重渲染

2、父组件状态更新 → handleClick 引用不变 → Chart 不重渲染

3、点击图表 → 触发缓存的handleClick

五、使用误区与最佳实践

常见错误:

javascript 复制代码
// ❌ 错误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,避免在简单组件中过度优化。