介绍
在优化 React 应用程序性能时,开发人员通常会使用 useMemo 和 useCallback。
人们很容易将每个对象和函数包装在这些钩子中,假设缓存输出将自动加速应用程序。然而,这是一个常见的误解。
不必要的记忆不仅会使代码更难阅读,而且还会使代码变得更难阅读。它增加了浅依赖项比较和垃圾收集内存分配的性能开销。
本文概述了这些钩子有益的具体场景以及何时应避免它们。
1. 记忆的隐性成本
useMemo 和 useCallback 都不是自由操作。它们在每个渲染周期都会产生运行时成本:
- 依赖关系比较开销:
在每次渲染时,React 必须浅比较(使用
Object.is)依赖项数组中的每个值。执行这些数组检查会增加 CPU 开销。 - 内存保留: 保留对先前函数或计算值的引用可以防止它们被垃圾收集,从而增加应用程序的内存占用。
- 复杂性和错误: 错误配置的依赖数组可能会导致陈旧的闭包,其中函数引用过时的状态或道具。
2. useMemo 使用指南
仅在以下两种情况下使用 useMemo:
场景 A:计算量大的任务
将其用于 CPU 密集型操作,例如对大型数组进行排序、过滤复杂数据集或运行繁重的映射操作。
- 推荐用例:
// Memoize search calculations on large lists const filteredItems = useMemo(() => { return heavySearch(items, query); }, [items, query]);
场景 B:保留 React.memo 的对象引用
React 在每次渲染时重新实例化组件内部的对象声明,更改它们的内存地址。
如果一个对象作为 prop 传递给使用 React.memo 优化的子组件,则子组件无论如何都会重新渲染,因为对象引用发生了变化。使用 useMemo 保持引用稳定。
- 推荐用例:
// Stabilize the options object reference const options = useMemo(() => ({ color: themeColor }), [themeColor]); // Sibling components optimized with React.memo will not re-render unnecessarily return <HeavyChildComponent options={options} />;
3. useCallback使用指南
useCallback 的目的不是防止函数重新实例化,而是跨渲染保留函数的内存引用。
在这种情况下仅应用 useCallback :
将回调传递给记忆的子组件
如果父组件重新呈现,则在其内部声明的任何函数都会重新创建,从而更改它们的引用。如果这些回调作为 props 传递给包装在 React.memo 中的子组件,则无论记忆如何,子组件都将重新渲染。
使用 useCallback 保持引用稳定。
推荐用例:
// Stabilize click callback references const handleClick = useCallback(() => { console.log("Button clicked!"); }, []); // Prevents re-renders if MemorizedButton uses React.memo return <MemorizedButton onClick={handleClick} />;重要提示: 如果子组件未包装在
React.memo中,当父组件包装在React.memo中时,无论您是否使用useCallback,它都会重新渲染。在这种情况下,钩子会增加不必要的开销。
结论:React 19 和 React 编译器
随着 React 19 的发布,React 编译器 (React Forget) 在构建时自动处理记忆。这减少了手动写入 useMemo 和 useCallback 的需要。
手动优化时:
- 首先进行配置文件以验证计算是否确实导致渲染延迟。
- 除非目标子组件使用
React.memo,否则避免包装回调。 - 优先考虑编写干净、简单的 JavaScript 逻辑,而不是先发制人的优化。
遵循这些准则可以使您的 React 代码具有可读性和高性能。

