文章目录
一句话理解场景 1:防止子组件不必要的重新渲染场景 2:在 useEffect 中依赖一个函数场景 3:结合自定义 Hook 传入回调场景 4:配合 useMemo / 复杂计算逻辑场景 5:与性能优化库结合(例如虚拟列表、表格)注意事项总结
在 WHAT – React 函数与 useMemo vs useCallback 我们简单了解过 useCallback。今天我们来深入理解一下。
是 React 中一个非常重要但容易被误解的 Hook。今天我们通过 多个典型场景 来理解它的用途和原理。
useCallback
一句话理解
会返回一个 记忆化(缓存)版本的回调函数,
useCallback(fn, deps)
只有当依赖项 () 变化时,才会返回新的函数引用。
deps
场景 1:防止子组件不必要的重新渲染
这是最常见的场景。
问题
当父组件重新渲染时,所有在它内部定义的函数都会被重新创建。
import React, { useState } from 'react';
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
console.log('✅ Child render');
return <button onClick={onClick}>Click me</button>;
});
export default function App() {
const [count, setCount] = useState(0);
const handleClick = () => console.log('clicked');
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child onClick={handleClick} />
</div>
);
}
👉 每次点击“+1”,父组件重新渲染, 被重新创建(函数引用变了)。
handleClick
即使 用了
Child,它也会重新渲染,因为
React.memo 变了。
onClick
解决:使用
useCallback
const handleClick = useCallback(() => console.log('clicked'), []);
✅ 现在 的引用在依赖
handleClick 不变时保持稳定。
[]
父组件更新时, 不会重新渲染。
Child
场景 2:在 useEffect 中依赖一个函数
如果你把函数直接写在组件里,而又在 中依赖它,会导致无限循环。
useEffect
useEffect(() => {
fetchData();
}, [fetchData]); // 🚨 fetchData 每次都是新函数,会导致无限请求
✅ 使用 固定函数引用:
useCallback
const fetchData = useCallback(() => {
// fetch something
}, []);
useEffect(() => {
fetchData();
}, [fetchData]); // ✅ 不会无限循环
场景 3:结合自定义 Hook 传入回调
例如在一个自定义 Hook 里需要外部传一个回调。
function useEventListener(event: string, handler: () => void) {
useEffect(() => {
window.addEventListener(event, handler);
return () => window.removeEventListener(event, handler);
}, [event, handler]);
}
⚠️ 如果 每次都是新函数(没用
handler),
useCallback
那每次渲染都会移除再添加一次事件监听。
✅ 改用:
const handleKeyDown = useCallback(() => {
console.log('Pressed key');
}, []);
useEventListener('keydown', handleKeyDown);
场景 4:配合 useMemo / 复杂计算逻辑
比如你在一个 memoized 的计算中依赖一个回调:
const computeValue = useCallback((x: number) => x * 2, []);
const result = useMemo(() => computeValue(10), [computeValue]);
这样可以确保 不会频繁变化导致
computeValue 重新计算。
useMemo
场景 5:与性能优化库结合(例如虚拟列表、表格)
像 Ant Design Table、react-window 等组件会用到 ,
onRow,
onClick 等回调。
rowRenderer
如果不使用 ,每次渲染都会创建新的函数,造成性能浪费或无法缓存。
useCallback
const onRowClick = useCallback((record) => {
console.log('Clicked row:', record);
}, []);
注意事项
不是所有函数都要用
useCallback
它会增加内存开销和心智负担。
一般只在以下场景使用:
函数传给 的子组件;函数作为
React.memo 依赖;函数传给性能敏感组件(虚拟列表、大表格等)。
useEffect
依赖数组很重要!
若函数中引用了外部状态变量,必须把它们加进依赖数组中。
const handleClick = useCallback(() => {
console.log(count);
}, [count]); // ✅ count 是依赖
≈
useCallback(fn, deps)
useMemo(() => fn, deps)
本质上 是
useCallback 的一个语法糖。
useMemo
总结
| 场景 | 是否适合用 useCallback | 说明 |
|---|---|---|
| 子组件 props 是函数且使用 React.memo | ✅ | 避免子组件重复渲染 |
| 函数放在 useEffect 依赖中 | ✅ | 避免无限循环 |
| 自定义 Hook 接受回调 | ✅ | 避免重复副作用 |
| 函数只在本组件使用 | ❌ | 没必要缓存 |
| 简单 onClick/onChange | ❌ | 直接写即可 |