)
Polar 前端性能优化指南不要在 useMemo 中包裹简单表达式Vercel 重渲染优化规则详解【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar导读本文围绕 Polar 仓库中 Vercel React 最佳实践技能包vercel-react-best-practices的一条核心规则展开当表达式足够简单、且计算结果为基本类型boolean、number、string时不要用useMemo包裹。这条规则属于重渲染优化Re-render Optimization类别用于消除每次渲染中不必要的 hook 调用与依赖比较开销。读完本文你将掌握判断什么该 memo、什么不该 memo的量化标准并能对照 Polar Web 前端源码中的真实案例识别并修正同类反模式。规则原文Do not wrap a simple expression with a primitive result type in useMemo该规则的完整定义位于 rerender-simple-expression-in-memo.mdfrontmatter 元数据如下titleDo not wrap a simple expression with a primitive result type in useMemoimpactLOW-MEDIUM低-中影响impactDescriptionwasted computation on every render每次渲染都会浪费一次计算tagsrerender, useMemo, optimization规则的核心论断非常直接当一个表达式很简单只包含少量逻辑或算术运算符且结果类型为基本类型boolean、number、string时不要用useMemo包裹它。调用useMemo并比较 hook 依赖所消耗的资源可能比表达式本身还要多。这条规则出自 Vercel 工程团队维护的 React/Next.js 性能优化指南是64 条规则、8 大类别中的一员见 SKILL.md。它属于第 5 优先级类别Re-render Optimizationrerender-前缀影响等级 MEDIUM核心目标是减少不必要的重渲染最小化浪费的计算并提升 UI 响应性见 _sections.md。反模式示例给||表达式套上 useMemo规则给出了一个典型的错误写法——在Header组件中把两个布尔值的||运算塞进useMemofunction Header({ user, notifications }: Props) { const isLoading useMemo(() { return user.isLoading || notifications.isLoading }, [user.isLoading, notifications.isLoading]) if (isLoading) return Skeleton / // return some markup }为什么这是反模式表达式本身只是两次属性读取加一次||运算是 O(1) 级别的微操作useMemo的调用开销包括创建并传入依赖数组、每次渲染时对新旧依赖做Object.is逐一比较、比较失败后执行回调并写入缓存值对比之下比较依赖的开销往往大于直接重新计算表达式的开销memo 化反而更慢依赖数组[user.isLoading, notifications.isLoading]本身又增加了代码噪音且要求开发者正确维护依赖列表一旦遗漏或冗余都会引入隐性 bug。正确写法直接内联计算规则给出的推荐写法是去掉useMemo让表达式在渲染期间自然求值function Header({ user, notifications }: Props) { const isLoading user.isLoading || notifications.isLoading if (isLoading) return Skeleton / // return some markup }在 React 的渲染模型中组件每次渲染都会重新执行函数体对于这种廉价表达式每次渲染重算一次的成本远低于 hook 依赖比较的成本。同时去掉useMemo还消除了依赖数组的维护负担代码更短、更易读、更不易出错。规则适用判定清单判断一个表达式是否应该去掉useMemo可以逐条核对判定维度应该去掉 useMemo可以保留 useMemo表达式复杂度少量逻辑/算术运算符\|\|、、!、、等大数组遍历、对象/数组构建、排序、递归等重量级计算结果类型基本类型boolean、number、string非基本类型对象、数组需要引用稳定性来配合memo子组件依赖比较成本依赖数量多、每次渲染都变化依赖极少变化或计算本身成本显著高于比较每次渲染是否用到总是用于 JSX 或分支判断只在特定条件下才需要结合仓库源码Polar Web 中的真实案例这条规则在 Polar Web 前端clients/apps/web/src中既有反面教材也有遵循规则的正面示例非常适合作为实战对照。案例一可用 useMemo 包裹的简单布尔表达式在 CustomerPortalOrder.tsx 中退款状态的判断就被包进了useMemoconst isPartiallyOrFullyRefunded useMemo(() { return order.status partially_refunded || order.status refunded }, [order])对照本文规则这是一个仅含两个比较运算符 一个||的布尔表达式结果类型为boolean属于典型的简单表达式 基本类型结果。若按规则重构可简化为const isPartiallyOrFullyRefunded order.status partially_refunded || order.status refunded需要说明的是该组件在此处使用useMemo的意图可能是让依赖只跟踪order.status的变更但依赖数组写的是整个order对象——一旦order上任何字段变化都会触发重新计算反而放大了依赖比较的粒度。这与同技能包中 rerender-dependencies.md收窄 Effect 依赖使用基本类型依赖的教训也相呼应。案例二遵循规则的内联派生布尔值有意思的是同一个文件紧接着就用内联计算的方式处理了另外两个派生布尔值CustomerPortalOrder.tsx// Seats management const hasSeatBasedOrder order.seats order.seats 0 // Check customer portal settings for seat management visibility const portalSettings order.product?.organization.customer_portal_settings const showSeatManagement portalSettings?.subscription.update_seats truehasSeatBasedOrder与showSeatManagement都是简单表达式 布尔结果这里直接内联计算、不经过任何 hook正是本规则推荐的做法。同一组件中两种写法并存恰好构成了一组直观的代码评审对照。案例三该保留 useMemo 的重量级计算对比并非所有useMemo都该删。在 AIProductChat.tsx/dashboard/[organization]/(header)/products/new/ai/AIProductChat.tsx#L37-L57) 中hasRedirectedToManualSetup与isFinished需要对messages做两层嵌套的.some()遍历每次都要扫描消息及其 parts 数组const hasRedirectedToManualSetup useMemo(() { return messages.some((message) message.parts.some( (part) part.type tool-redirectToManualSetup (part.state input-available || part.state output-available), ), ) }, [messages])虽然结果仍是boolean但计算成本是 O(n×m) 的数组遍历远超少量运算符的简单表达式定义且依赖只有messages一个引用比较成本低。这种场景保留useMemo是合理的——它才是规则所说的复杂计算。同样CostsBandedChart.tsx 中hasDecimalValues需要对整组数据进行 4 次取模判断也属于数组遍历型计算保留 memo 无可厚非。与同技能包其他规则的协同本规则并非孤立存在它与rerender-类别的其他规则共同构成一套判断体系rerender-derived-state.md优先订阅派生布尔值而非连续值如用isMobile布尔值替代逐像素变化的width。本文规则的内联派生布尔正是其最简形式rerender-dependencies.md依赖应使用基本类型而非对象删掉不必要的useMemo后这条规则自然也不再需要操心rerender-memo.md重计算应提取为 memo 化组件配合memo()提前 return 跳过计算而非用useMemo缓存中间值。关于 React Compiler 的重要说明rerender-memo.md 末尾明确指出如果项目已启用 React Compiler则手动memo()与useMemo()均无必要编译器会自动优化重渲染。因此在 Polar Web 这类基于 Next.js/React 的前端中做代码评审时应先确认项目是否开启 React Compiler再决定是否按本文规则清理手写 memo。实战建议如何在代码评审中落地优先排查布尔/数字/字符串 ||、、三元、比较运算符的useMemo这类最可能命中本规则重构时直接删除 hook 调用将表达式内联到渲染函数体重构后跑一遍组件测试或渲染对比确认渲染结果一致——表达式语义不变结果必然一致风险极低对涉及数组遍历、对象构建、字符串拼接/格式化等成本不可忽略的计算保留useMemo但注意依赖数组应使用基本类型如order.status而非整个对象如order在团队内把本规则连同 SKILL.md 的 64 条规则一起作为 React/Next.js 代码生成与评审的默认标准。总结不要在useMemo中包裹简单表达式是 Vercel 性能规则集中最容易落地的一条它不涉及复杂的数据流改造只是要求开发者对计算成本与hook 开销做出理性对比。以 Polar Web 的 CustomerPortalOrder.tsx 为镜同文件内既有待重构的useMemo布尔表达式也有已经内联计算的正确示例是团队内进行规则宣讲的绝佳样板。记住核心判据——结果是否为基本类型、计算是否为微操作——即可在绝大多数场景下做出正确选择。【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考