前言:useCallback 和 useMemo,你真的用对了吗?
今天来聊聊 React 中两个容易混淆的 Hook:useCallback 和 useMemo。很多人用了很久 React,却还是搞不清楚它们的具体区别和使用场景。别担心,看完这篇文章,你一定会豁然开朗!
useCallback:不只是缓存函数
useCallback 的作用
简单来说,useCallback 就是用来缓存函数的。它返回一个记忆化的回调函数,只有当依赖项发生变化时,这个函数才会重新创建。
scss
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b], // 依赖项
);
useCallback 的应用场景
1. 性能优化:避免子组件不必要的重渲染
这是 useCallback 最常用的场景。当父组件传递函数给子组件时,如果父组件重渲染,每次都会创建新的函数实例,导致子组件即使用了 React.memo 也会重新渲染。
javascript
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log('按钮被点击了');
}, []); // 空依赖数组表示永远不会重新创建
return (
<div>
<button onClick={() => setCount(count + 1)}>增加:{count}</button>
<Child onClick={handleClick} />
</div>
);
};
// 子组件用 React.memo 包裹
const Child = React.memo(({ onClick }) => {
console.log('子组件渲染了');
return <button onClick={onClick}>子按钮</button>;
});
2. 作为其他 Hook 的依赖项
当函数被作为 useEffect、useMemo 等 Hook 的依赖项时,使用 useCallback 可以避免 effect 不必要的重复执行。
scss
const fetchData = useCallback(async () => {
const response = await fetch('/api/data');
return response.json();
}, []); // 注意:这里如果真的有依赖项,一定要如实填写
useEffect(() => {
fetchData();
}, [fetchData]); // 因为 fetchData 被 useCallback 缓存,effect 不会频繁执行
useCallback 的常见误区
不要滥用 useCallback! 很多开发者习惯给每个函数都包上 useCallback,这其实是不对的。
useCallback 本身也有性能开销(比较依赖项),只有在真正需要时使用它:
- 函数作为子组件的 props
- 函数作为其他 Hook 的依赖项
- 函数内部有复杂计算(但这种情况其实更适合 useMemo)
javascript
// 没必要用 useCallback!
const simpleFunction = () => {
console.log('很简单的方法');
};
// 需要用 useCallback
const complexFunction = useCallback(() => {
// 复杂计算或操作
}, [dependency]);
useMemo:缓存计算结果
useMemo 的作用
useMemo 用来缓存计算结果,只有当依赖项发生变化时,才会重新计算。
scss
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo 的应用场景
1. 优化昂贵计算
当有计算量很大的函数时,使用 useMemo 可以避免每次渲染都重新计算。
javascript
const ExpensiveComponent = ({ data }) => {
// 只有 data 变化时才会重新计算
const processedData = useMemo(() => {
console.log('正在进行昂贵计算...');
return data.map(item => ({
...item,
fullName: `${item.firstName} ${item.lastName}`,
score: calculateScore(item) // 假设这是个复杂计算
}));
}, [data]);
return <div>{/* 使用 processedData */}</div>;
};
2. 避免对象和数组的重复创建
在 JavaScript 中,每次渲染都会创建新的对象和数组,即使内容完全一样。这可能导致子组件不必要的重渲染。
javascript
const Parent = () => {
const [count, setCount] = useState(0);
// 不使用 useMemo:每次渲染都会创建新对象
// const config = { type: 'button', style: 'primary' };
// 使用 useMemo:只有依赖变化时才创建新对象
const config = useMemo(() => ({
type: 'button',
style: 'primary'
}), []); // 空依赖:永远不会重新创建
return (
<div>
<button onClick={() => setCount(count + 1)}>点击次数:{count}</button>
<Child config={config} />
</div>
);
};
const Child = React.memo(({ config }) => {
console.log('子组件渲染了');
return <button className={config.style}>子按钮</button>;
});
useCallback vs useMemo:到底有什么区别?
简单来说:
- useCallback 缓存函数本身
- useMemo 缓存函数的返回值
看个例子就明白了:
scss
// 这两个表达式是等价的:
useCallback(fn, deps);
useMemo(() => fn, deps);
useCallback 返回的是函数,useMemo 返回的是函数执行的结果。
结语:合理使用,不要滥用
useCallback 和 useMemo 都是性能优化工具,但并不是用的越多越好。不当使用反而会让代码更复杂,甚至降低性能。
使用原则:
- 先写没有优化的代码,发现有性能问题后再优化
- 使用 React DevTools 的 Profiler 分析性能瓶颈
- 不要为了优化而优化,保持代码可读性更重要
记住:最好的优化是不需要优化。写出清晰、简洁的代码比过度使用优化 Hook 更重要!