缓存机制:区分值与函数的引用稳定
React 的渲染机制中,父组件状态更新会触发自身及其所有子组件的重新渲染。默认情况下,每次渲染都会重新执行组件函数体内的所有逻辑,包括变量声明、函数定义和复杂计算。useMemo 与 useCallback 的核心区别在于缓存对象的不同:useMemo 用于缓存任意类型的计算结果(如对象、数组或数值),而 useCallback 专门用于缓存函数引用。两者都依赖第二个参数——依赖数组。当依赖数组中的值在前后两次渲染中保持严格相等(Object.is)时,React 会直接返回上一次缓存的值或函数,跳过重新计算或重新创建。理解这一机制的关键在于认识到:React 的重新渲染本身并不昂贵,真正消耗性能的是渲染后触发的 DOM 更新或子组件的无效重绘。只有当缓存的值或函数作为 props 传递给被 React.memo 包裹的子组件时,引用稳定才能有效阻断不必要的子组件渲染链路。

useMemo:优化昂贵计算与稳定对象引用
当组件需要处理大规模数据或执行高开销运算时,直接在内联逻辑中计算会导致每次渲染都重复执行。例如,在一个包含上万条用户记录的列表中,根据关键词进行筛选并排序的操作属于典型的昂贵计算。使用 useMemo 可以将该计算逻辑包裹起来:const filteredList = useMemo(() => { return rawData.filter(...).sort(...); }, [rawData, keyword]);。只有当 rawData 或 keyword 发生变化时,过滤与排序才会重新执行;否则直接返回缓存结果。此外,useMemo 常用于稳定对象引用。若父组件每次渲染都通过字面量创建新对象(如 { theme: 'dark' }),即使内容相同,引用也会改变,导致子组件重新渲染。通过 useMemo 包装该对象,并配合子组件的 React.memo 进行浅层比较,即可在依赖不变时彻底跳过子组件的渲染流程,显著提升列表渲染性能。

useCallback:稳定函数引用并配合 memo
在父子组件通信中,父组件向子组件传递事件处理函数是常见模式。若直接在 JSX 中定义内联函数(如 onClick={() => handleClick(id)}),每次父组件渲染都会生成全新的函数实例。即使子组件使用了 React.memo,由于 props 中的函数引用发生变化,浅比较依然会判定为 props 更新,从而触发子组件无效重渲染。useCallback 的作用正是解决此问题:const stableHandler = useCallback(() => { doSomething(id); }, [id]);。它将函数定义与依赖项绑定,仅在依赖项变化时生成新引用。配合 React.memo 使用时,只要依赖项未变,子组件接收到的函数引用就保持一致,浅比较通过,渲染被跳过。需要注意的是,若回调函数内部引用了父组件的状态或 props,必须将其准确列入依赖数组,否则会导致闭包陷阱,读取到过期的旧值。正确平衡依赖项与缓存策略,是发挥 useCallback 效能的关键。

性能验证与常见误区:不要为了优化而优化
性能优化必须基于真实数据而非主观猜测。在开发阶段,应使用 React DevTools Profiler 记录组件的渲染次数、渲染耗时及调用链,对比优化前后的火焰图差异。若某组件渲染耗时仅几毫秒且无子组件,强行添加 useMemo 或 useCallback 反而会增加内存开销与依赖比较成本。常见误区包括:遗漏依赖项导致状态不同步;对轻量级计算(如简单字符串拼接)进行记忆化;以及未配合 React.memo 使用,导致缓存的引用无法阻断渲染。此外,随着 React 19 及 React Compiler 的引入,编译器能够在构建阶段自动分析组件依赖并插入记忆化逻辑,大幅减少手动优化的必要性。因此,现代 React 开发应遵循先测量、后优化的原则,仅在 Profiler 明确显示性能瓶颈或引用不稳定导致严重重渲染时,才针对性地引入手动记忆化策略。

