用好useMemo让你的React组件性能起飞的秘密武器

码农义霞 框架 阅读 3,051
赞 11 收藏
二维码
手机扫码查看
反馈

又踩坑了,useMemo居然是性能瓶颈?

最近在优化一个React项目的时候,我发现了一个很诡异的问题。有个列表页的渲染速度特别慢,尤其是在数据量稍微大一点的时候,页面直接卡到怀疑人生。折腾了半天发现,问题居然出在useMemo上。

用好useMemo让你的React组件性能起飞的秘密武器

说起来有点丢人,我本来是想用useMemo来优化性能的,结果反而搞出了性能问题。当时我就懵了,这不科学啊!后来经过一番排查,总算把问题解决了,顺便还学到了不少东西。

排查过程:一顿操作猛如虎,发现问题竟在我

最开始我以为是API请求太慢,或者后端返回的数据太大。于是先检查了网络请求,发现响应时间其实挺正常的,也就两三百毫秒。然后我又怀疑是不是组件结构太复杂,但看了一下组件树,也没啥特别深的嵌套。

这里我踩了个坑,浪费了不少时间。我一度以为是React DevTools显示有问题,换了好几个版本来回测试,甚至还试了下Profiler工具,结果还是没找到原因。后来才发现,问题根本不在这些地方。

直到我仔细看了下代码,才注意到问题出在一个看似无害的useMemo调用上:

const processedData = useMemo(() => {
  return rawData.map(item => ({
    ...item,
    computedValue: someExpensiveCalculation(item)
  }))
}, [rawData]);

表面上看这段代码没啥问题,对吧?每次rawData变化时重新计算processedData,逻辑很清晰。但问题是,这里的someExpensiveCalculation函数实在太重了,而且rawData是个很大的数组,每次变化都会触发整个数组的重新计算。

核心代码就这几行,优化后的写法

最后我改成了这样:

const processedData = useMemo(() => {
  return rawData.map(item => {
    const cachedValue = cache.get(item.id);
    if (cachedValue) return cachedValue;

    const newValue = {
      ...item,
      computedValue: someExpensiveCalculation(item)
    };
    cache.set(item.id, newValue);
    return newValue;
  });
}, [rawData]);

这里加了个简单的缓存机制,用Map来存储已经计算过的值。这个改动看起来简单,但效果立竿见影。之前需要几百毫秒的计算,现在基本都在几十毫秒内完成了。

另外我还发现了个小细节:原来的代码里,rawData其实经常只是部分更新,但因为引用变了,导致整个数组都被重新计算。所以我又加了个小优化:

const isShallowEqual = (arr1, arr2) => 
  arr1.length === arr2.length && arr1.every((item, index) => item === arr2[index]);

const processedData = useMemo(() => {
  // 同上
}, [isShallowEqual(rawData, prevRawData) ? prevRawData : rawData]);

这个浅比较虽然增加了点复杂度,但在特定场景下确实能减少不必要的计算。

技术细节唠叨几句

说到useMemo,很多人可能觉得它就是个简单的memoization工具,但其实它的行为还挺有意思的。React官方文档里提到过,useMemo并不会保证一定执行memoization,有时候出于性能考虑,React可能会选择忽略它。

这里需要注意的是,useMemo的主要作用其实是避免不必要的子组件渲染或复杂计算。但如果你滥用它,比如像我之前那样,在依赖项变化时执行大量计算,反而会得不偿失。

还有一个容易忽视的点是,useMemo的依赖数组其实是个浅比较。这意味着如果依赖项是个对象或数组,即使内容没变但引用变了,也会触发重新计算。所以很多时候我们需要配合一些不可变数据的技巧来使用。

踩坑提醒:这三点一定注意

  • 别盲目使用useMemo:不是所有计算都需要memoization,简单的计算直接写可能更高效。
  • 注意依赖项的变化:特别是对象和数组,引用变化会导致不必要的重新计算。
  • 慎用复杂的计算逻辑:如果计算本身就很重,最好考虑其他优化方案,比如Web Worker。

最后说个小插曲,改完之后其实还有个小问题:当数据量特别大的时候,内存占用会稍微高一点,因为缓存占用了额外的空间。不过考虑到实际业务场景,这个影响可以接受,暂时就这样了。

以上是我踩坑后的总结

这次经历让我对useMemo有了更深的理解。虽然官方文档都看过,但真遇到问题才知道其中的门道。如果你有更好的优化方案,或者也遇到过类似的问题,欢迎在评论区交流。

对了,最后再补充一句:React性能优化这块水挺深的,后续我打算写几篇相关的文章,包括如何正确使用React.memo、shouldComponentUpdate等,感兴趣的可以关注一下。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论