在使用 React 五年后,真正明白 useMemo 的意义

文化   2024-12-25 20:40   新加坡  

刚接触 useMemo 时,我以为它的核心作用就是用来做缓存。很多开发者都有类似误解:认为 useMemo 主要是为了性能优化,以避免组件中不必要的重新计算。然而,经历了五年的 React 开发,我才意识到 useMemo 的真正价值不只是性能——更关键的是,它能保持数据引用的稳定性,让组件行为更加可预期。

为什么 useMemo 不仅仅是缓存

普遍的误解是:useMemo 是个用来“记住”某些计算结果的魔法,使得相同输入不会重复计算。虽然这种理解在技术层面没错,但用 useMemo 的更大收益在于确保引用(Reference)的稳定性。这在需要将某些数据传递给自定义 Hook 或作为依赖项使用时尤为重要。

设想一个场景:组件需要计算出某个对象,然后将这个对象当作参数传给自定义 Hook。如果这个对象在每次渲染时都新建一个实例,即使内容相同,引用也不同,从而导致 Hook 误以为数据每次都变了。这可能引发不必要的副作用或反复渲染。

我的经验教训:用对 useMemo

有一次,我在实现一个自定义计算 Hook 时遇到了类似问题。我在组件中内联构建了一个对象,把已有的 bookingFields 和 values 合并后传给 useCalculations:

const existingFields = {
    ...bookingFields,
    ...values,
};
const { calculations } = useCalculations(existingFields);

表面上看,这段代码似乎没啥问题。但不久后,我发现组件陷入了不停的重新渲染循环,计算结果也一直不稳定。问题的根源在于:虽然 existingFields 的内容没变,但每次渲染都会生成一个新的对象引用。React 在比较依赖项时是根据引用来判断变化的,所以自定义 Hook 认为数据“每次都更新了”。

拯救者 useMemo 登场

为了解决这个问题,我用 useMemo 将这个对象的创建过程包裹起来,让它只有在依赖数据(bookingFields 和 values)改变时才重新生成对象:

const existingFields = useMemo(
    () => ({
        ...bookingFields,
        ...values,
    }),
    [bookingFields, values]
);

改写之后,引用稳定下来,我的自定义 Hook 终于不再频繁触发重复计算,也不再让组件重复渲染。一切变得井然有序。🎉

何时使用 useMemo

  1. 避免不必要的重渲染:当需要将派生出来的对象或数组作为依赖项传给自定义 Hook 或子组件时,可以用 useMemo 稳定它的引用,从而避免组件不断重新渲染。
    示例:有个子组件要接收一个数组作为 prop,用 useMemo 确保这个数组只有在源数据改变时才更新,而不是在每次父组件渲染时都生成新数组。

  2. 稳定依赖:在 useEffect 或 useCallback 中使用依赖项时,如果这些依赖项是对象或函数引用,useMemo 能确保在依赖项未实际变动时不触发不必要的副作用。

  3. 复杂计算:对于计算量较大的数据处理,useMemo 可以确保只有在相关依赖变动时才重新计算,减少性能浪费。

何时不该使用 useMemo

如果你的计算非常简单,或不依赖于外部动态数据,那么 useMemo 可能只是增加代码的复杂性,而无实质收益。不必要的 memo 化会使代码难以理解,并且可能没有明显的性能提升。

核心收获

useMemo 的真正价值在于保持引用的稳定性,进而保证组件行为的可预测性。当我们减少了无意义的重复计算和渲染,性能自然得以提升。但要记住,不要一上来就为了“优化”而过度使用 useMemo。清晰的思路是:先确保组件的行为正确、可控,然后在需要时再考虑用 useMemo 来避免不必要的变化。

下次你准备用 useMemo 时,不妨问问自己:是因为需要稳定数据引用以避免无谓的重复工作,还是只是在做无意义的“过早优化”?只有真正理解它的用武之地,才能让 useMemo 在你的 React 项目中发挥最大价值。


最后:
React Hook 深入浅出
CSS技巧与案例详解
vue2与vue3技巧合集
VueUse源码解读



大迁世界
掘金LV8,思否10万+的作者。一个热爱前端的创业者。
 最新文章