文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

现在停止滥用useMemo吧!

2024-11-29 22:15

关注

在React应用中过度优化真的是一种噩梦。我们每天都要面对大量无用的优化,这些优化意在使代码变得“神秘”。一些开发人员甚至将 useMemo 和 useCallback 纳入他们的默认风格指南中,以简化事情。不要陷入这种迷思,因为 useMemo 甚至可能会减慢你的应用程序速度!记住,记忆化并不是免费的。

在这篇文章中,向你展示大多数开发人员如何过度使用 useMemo 钩子,并提供一些避免这种情况的技巧。当我第一次意识到我犯了这些错误时,我感到非常愚蠢。话不多说,开始吧!

为什么我们使用useMemo?

useMemo 是一个允许我们在组件重新渲染之间缓存计算结果的钩子。毫无疑问,它仅用于性能原因,并且应该与其他技术如 React.memo、useCallback、debouncing、并发渲染等一起使用。

尽管在某些情况下,这个钩子确实有帮助,并且发挥了重要作用,但大多数开发人员却错误地使用它。他们将每个变量都用 useMemo 包裹起来,希望随机的优化能带来成功。不出所料,这种方法只会导致代码可读性差和内存使用增加。

另一个需要记住的重要事项是,useMemo 只有在重新渲染阶段才有价值。在初始化期间,记忆化(memoization)会减慢应用程序的速度,并且这种影响会逐渐累积。这就是我所说的“记忆化并不是免费的”的意思。

什么时候它没有用甚至有害?

现在,看看下面的例子。这些都是我从一个项目中拿来的真实代码片段。你能说出哪个useMemo的使用实际上有意义吗?

export const NavTabs = ({ tabs, className, withExpander }) => {
  const currentMainPath = useMemo(() => {
    return pathname.split("/")[1];
  }, [pathname]);
  const isCurrentMainPath = useMemo(() => {
    return currentMainPath === pathname.substr(1);
  }, [pathname, currentMainPath]);

  return (
    
      
        {isCurrentMainPath ? (
          t(currentMainPath)
        ) : (
          
            {t(currentMainPath)}
          
        )}
      
    
  );
};

通常我们在两种情况下使用 useMemo:记住一个引用并将其传递给 memo 组件,或缓存一些昂贵的计算。

现在想一想,我们在上面的例子中优化了什么?我们有原始值,并没有将任何更深层次的内容传递到组件树中,所以我们不需要保留引用。而且.split和===似乎不是需要记忆化的复杂计算。因此,我们可以轻松地在这个例子中去掉useMemo,节省一些文件空间 :)

export const Client = ({ clientId, ...otherProps }) => {
  const tabs = useMemo(
    () => [
      {
        label: t("client withdrawals"),
        path: `/clients/${clientId}/withdrawals`
      },
      ...
    ],
    [t, clientId]
  );
  
  ...
  
  return (
    <>
      ...
      
    
  )
}

export const NavTabs = ({ tabs, className, withExpander }) => {
  return (
    
      {tabs.map((tab) => (
        
          {tab.label}
        
      ))}
    
  );
};

在上面的例子中,我们将tabs变量记忆化,然后将其传递给 NavTabs。你认为优化tabs创建的主要意图是什么?这根本不是一个计算问题,所以实现它的人可能是想要保留引用并避免 NavTabs 的过度重新渲染。这在这里是正确的做法吗?

首先,NavTabs 是一个轻量级组件,可以多次重新渲染而不影响性能。其次,即使它是一个重量级组件,useMemo 也不会起作用。仅仅保留引用不足以防止 NavTabs 在每次Client组件重新渲染时重新渲染。为了优化性能,我们应该用React.memo包裹 NavTabs。

memo 函数返回我们组件的记忆化版本。只要它的 props 没有改变,这个版本通常不会在其父组件重新渲染时重新渲染。memo 使用浅比较props来决定组件是否应该更新。如果你有一些特定条件来决定组件何时应该重新渲染,你甚至可以指定自己的比较函数作为 memo 的第二个参数。

如何判断计算是否昂贵?

除非你在执行成千上万项复杂循环或计算阶乘,否则它可能不昂贵。你可以结合使用React Profiler和performance.now()来识别瓶颈,然后再应用优化技术。

避免在以下情况下使用 useMemo:

如何正确使用的技巧

React 组件每次重新渲染时都会运行其主体,这发生在其道具或状态发生变化时。通常情况下,我们希望避免在渲染过程中进行昂贵的计算,因为它们会减慢组件的运行速度。

然而,大多数计算仍然非常快,因此很难理解哪些地方确实需要使用 useMemo。要开始有效且有目的性地进行优化,首先应找出问题所在。此外,不要忘记其他技术,例如:useCallback、React.memo、debouncing、代码拆分、lazy-loading 等。

我们看下面一个非常简单的例子。doCalculation 是一个人为放慢速度的函数,因此它需要 500ms 才能返回一个随机数。那么我们在这里遇到了什么问题呢?是的,每次值更新时,我们都要重新计算我们的重函数,这使得使用输入变得非常困难。

function doCalculation() {
  const now = performance.now();
  while (performance.now() - now < 500) {
    // 停顿500毫秒
  }

  return Math.random();
}

export default function App() {
  const [value, setValue] = useState(0);
  const calculation = doCalculation();

  return (
    
setValue(e.target.value)} />
); }

我们如何解决这个问题?只需用useMemo包裹doCalculation而不加任何依赖项。

const calculation = useMemo(() => doCalculation(), []);

现场实例:

https://codesandbox.io/s/happy-grass-0mjpbw?from-embed。

来源:大迁世界内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯