用好useMemo让你的React组件性能起飞的秘密武器
又踩坑了,useMemo居然是性能瓶颈?
最近在优化一个React项目的时候,我发现了一个很诡异的问题。有个列表页的渲染速度特别慢,尤其是在数据量稍微大一点的时候,页面直接卡到怀疑人生。折腾了半天发现,问题居然出在useMemo上。
说起来有点丢人,我本来是想用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等,感兴趣的可以关注一下。

暂无评论