Featured image of post 停止在 React 中过度使用 useMemo 和 useCallbackFeatured image of post 停止在 React 中过度使用 useMemo 和 useCallback

停止在 React 中过度使用 useMemo 和 useCallback

分析 React 中 useMemo 和 useCallback 的隐藏性能开销,并建立何时应用记忆化的指南。

介绍

在优化 React 应用程序性能时,开发人员通常会使用 useMemouseCallback

人们很容易将每个对象和函数包装在这些钩子中,假设缓存输出将自动加速应用程序。然而,这是一个常见的误解。

不必要的记忆不仅会使代码更难阅读,而且还会使代码变得更难阅读。它增加了浅依赖项比较和垃圾收集内存分配的性能开销

本文概述了这些钩子有益的具体场景以及何时应避免它们。


1. 记忆的隐性成本

useMemouseCallback 都不是自由操作。它们在每个渲染周期都会产生运行时成本:

  1. 依赖关系比较开销: 在每次渲染时,React 必须浅比较(使用 Object.is)依赖项数组中的每个值。执行这些数组检查会增加 CPU 开销。
  2. 内存保留: 保留对先前函数或计算值的引用可以防止它们被垃圾收集,从而增加应用程序的内存占用。
  3. 复杂性和错误: 错误配置的依赖数组可能会导致陈旧的闭包,其中函数引用过时的状态或道具。

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) 在构建时自动处理记忆。这减少了手动写入 useMemouseCallback 的需要。

手动优化时:

  1. 首先进行配置文件以验证计算是否确实导致渲染延迟。
  2. 除非目标子组件使用 React.memo,否则避免包装回调。
  3. 优先考虑编写干净、简单的 JavaScript 逻辑,而不是先发制人的优化。

遵循这些准则可以使您的 React 代码具有可读性和高性能。