深入理解 React 的 useCallback:优化性能的关键技巧
·
1. 为什么需要 useCallback?
在 JavaScript 中,函数是引用类型,这意味着每次创建函数时都会生成新的内存引用。React 函数组件在每次渲染时,内部定义的函数都会重新创建,导致:
- 引用变化:即使函数内容相同,每次渲染都会生成新引用
- 子组件不必要渲染:如果函数作为 props 传递给优化过的子组件(使用
React.memo),会触发重新渲染 - useEffect 重复执行:如果函数是
useEffect的依赖项,会导致副作用重复触发
引用示例 1:函数引用对比
// 示例1:相同函数,不同引用
const func1 = () => console.log("Hello");
const func2 = () => console.log("Hello");
console.log(func1 === func2); // false - 不同引用
// 示例2:相同引用
const func = () => console.log("Hello");
const func3 = func;
const func4 = func;
console.log(func3 === func4); // true - 相同引用
2. useCallback 的基本用法
useCallback 通过缓存函数引用来优化性能:
语法解析
const memoizedFn = useCallback(
() => { /* 函数体 */ },
[dependencies] // 依赖项数组
);
引用示例 2:useCallback 保持引用稳定
function App() {
const [count, setCount] = useState(0);
// 普通函数 - 每次渲染新引用
const normalFn = () => console.log(count);
// useCallback 缓存函数 - 引用稳定
const memoizedFn = useCallback(() => {
console.log(count);
}, [count]); // 依赖 count
console.log("普通函数引用对比:", normalFn === normalFn); // false
console.log("memoized函数引用对比:", memoizedFn === memoizedFn); // true
return <Child fn={memoizedFn} />;
}
3. 依赖项深度解析
依赖项决定何时创建新函数引用:
引用示例 3:依赖项对引用的影响
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// 依赖 count,只有 count 变化时才会创建新引用
const countFn = useCallback(() => {
console.log(count);
}, [count]);
// 依赖 text,只有 text 变化时才会创建新引用
const textFn = useCallback(() => {
console.log(text);
}, [text]);
// 空依赖,永远保持相同引用
const stableFn = useCallback(() => {
console.log("Stable");
}, []);
return (
<>
<button onClick={() => setCount(c => c + 1)}>Change count</button>
<button onClick={() => setText(t => t + "a")}>Change text</button>
</>
);
}
4. 结合 React.memo 的优化实践
React.memo 通过浅比较 props 来避免不必要的渲染:
引用示例 4:引用变化对 React.memo 的影响
// 优化后的子组件
const MemoChild = React.memo(({ onClick }) => {
console.log("MemoChild rendered");
return <button onClick={onClick}>Click</button>;
});
// 普通子组件
const NormalChild = ({ onClick }) => {
console.log("NormalChild rendered");
return <button onClick={onClick}>Click</button>;
};
function Parent() {
const [count, setCount] = useState(0);
// 未优化的函数 - 每次渲染新引用
const normalFn = () => console.log("Normal click");
// 优化的函数 - 引用稳定
const memoizedFn = useCallback(() => {
console.log("Memoized click");
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Render Parent ({count})</button>
<NormalChild onClick={normalFn} />
<MemoChild onClick={memoizedFn} />
</div>
);
}
5. 性能优化对比实验
通过实际测量展示优化效果:
引用示例 5:性能对比测试
function HeavyComponent({ value }) {
// 模拟耗时渲染
const start = performance.now();
while (performance.now() - start < 2) {}
return <div>{value}</div>;
}
function TestApp() {
const [count, setCount] = useState(0);
// 未优化的处理函数
const unoptimizedHandler = () => {
console.log("Unoptimized handler");
};
// 优化的处理函数
const optimizedHandler = useCallback(() => {
console.log("Optimized handler");
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Re-render ({count})</button>
{/* 每次都会重新渲染 */}
<HeavyComponent value="未优化" onClick={unoptimizedHandler} />
{/* 使用 React.memo + useCallback 优化 */}
<React.memo(HeavyComponent) value="已优化" onClick={optimizedHandler} />
</div>
);
}
6. 常见误区与最佳实践
误区 1:滥用 useCallback
// 不好的写法:简单函数不需要 useCallback
const badExample = useCallback(() => {
setState(prev => !prev);
}, []);
// 好的写法:直接使用箭头函数
const goodExample = () => setState(prev => !prev);
误区 2:依赖项不完整
function App() {
const [count, setCount] = useState(0);
// 错误的写法:缺少 count 依赖
const badCallback = useCallback(() => {
console.log(count); // 闭包问题,总是打印初始值
}, []);
// 正确的写法
const goodCallback = useCallback(() => {
console.log(count); // 总是最新值
}, [count]);
return <button onClick={() => setCount(c => c + 1)}>Increment</button>;
}
7. 总结与决策流程图
何时使用 useCallback?
| 优化场景 | 推荐方案 | 引用变化规则 |
|---|---|---|
| 简单函数 | 直接定义 | 每次渲染新引用 |
| 传递给优化组件的函数 | useCallback | 依赖变化时新引用 |
| 复杂计算值 | useMemo | 依赖变化时新引用 |
| 事件处理函数 | 视情况使用useCallback | 按需控制引用 |
8. 扩展阅读
更多推荐

所有评论(0)