尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Polar 前端中的 memo 组件提取优化:把昂贵的计算移到提前 return 之后

Polar 前端中的 memo 组件提取优化:把昂贵的计算移到提前 return 之后 Polar 前端中的 memo 组件提取优化把昂贵的计算移到提前 return 之后【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar本指南源于 Polar 仓库内置的 Vercel React 最佳实践规则集.agents/skills/vercel-react-best-practices/rules/rerender-memo.md聚焦重渲染优化中的提取为 memo 化组件Extract to Memoized Components这一规则说明其背后的原理、正确的代码组织方式并结合仓库中的真实实现进行印证。文章围绕该规则讲解其适用场景、错误写法与正确写法以及它与其他相关优化手段的关系帮助你在使用 React/Next.js 的项目特别是像 Polar 这样的中大型应用中写出更高效、更可维护的组件。规则内容该规则的核心思想是把昂贵的计算提取到独立的 memo 化组件中从而在父组件中实现提前 return让昂贵计算在不需要时完全不执行。React 的 Hooks 规则要求 Hook 在组件顶层无条件调用。因此如果你在父组件中用useMemo计算头像即使后面发现处于 loading 状态要提前返回Skeleton /useMemo里的计算也已经执行了。错误的写法function Profile({ user, loading }: Props) { const avatar useMemo(() { const id computeAvatarId(user) return Avatar id{id} / }, [user]) if (loading) return Skeleton / return div{avatar}/div }问题即使loading true此时根本不需要头像computeAvatarId(user)也已经执行了。正确的写法const UserAvatar memo(function UserAvatar({ user }: { user: User }) { const id useMemo(() computeAvatarId(user), [user]) return Avatar id{id} / }) function Profile({ user, loading }: Props) { if (loading) return Skeleton / return ( div UserAvatar user{user} / /div ) }关键点loading分支在渲染树中的位置早于UserAvatar因此 loading 时该组件根本不会被渲染computeAvatarId完全不会执行UserAvatar用memo包裹当user引用不变时比如父组件因loading变化而重渲染UserAvatar会跳过重渲染复用上次的渲染结果即使user变化需要重渲染useMemo也能保证只有依赖变化时才重新计算computeAvatarId。也就是说memo防止的是不必要的重渲染useMemo防止的是不必要的重复计算两者组合使用效果最佳。仓库中的真实印证Polar 仓库前端位于 clients/apps/web在实践中运用了这些模式。1. 用 memo 包裹接受字符串 props 的子组件ProductGrantsFeed.tsx 中定义了一个展示功能开关授予状态的FlagRow// ── Memoized flag row ───────────────────────────────────────────────────────── const FlagRow memo( ({ label, isGranted }: { label: string; isGranted: boolean }) ( div classNameflex items-center justify-between gap-x-4 rounded-lg px-3 py-2 {/* ... */} Switch checked{isGranted} / /div ), ) FlagRow.displayName FlagRow它接受的是label: string和isGranted: boolean这样的原始类型props因此只要父组件中某个行的这两个值没有变化memo就能让该行跳过重渲染。这种对列表中的每一行做 memo 化的做法正是提取为 memo 化组件在大列表渲染场景下的典型应用。2. 自定义比较函数 设置 displayNameMemoizedMarkdown.tsx 展示了memo的第二个参数——自定义比较函数import Markdown from markdown-to-jsx import { memo } from react import { markdownOptions } from /utils/markdown export const MemoizedMarkdown memo( ({ content }: { content: string }) { return Markdown options{markdownOptions}{content}/Markdown }, (prevProps, nextProps) prevProps.content nextProps.content, ) MemoizedMarkdown.displayName MemoizedMarkdown说明默认情况下memo做浅比较这里传入自定义比较函数prevProps.content nextProps.content只有当 markdown 内容字符串真正变化时才重渲染由于 Markdown 渲染markdown-to-jsx解析属于相对昂贵的计算这样的 memo 化能避免内容未变时反复执行解析与渲染为 memo 化组件显式设置displayName也有利于 React DevTools 中的调试。3. 依赖数组与 memo 组合使用Polar 的 dashboard 分析页如 CostsPage.tsx/dashboard/[organization]/(header)/analytics/costs/CostsPage.tsx)大量使用useMemo来计算派生数据日期范围、客户 ID 列表、汇总统计等依赖数组精确控制计算时机。这与规则中useMemo(() computeAvatarId(user), [user])的思路一致只让真正变化的依赖触发重新计算。从源码结构看Polar 遵循了重计算放到useMemo、跨渲染复用组件用memo、早退分支放在计算之前这套 Vercel 推荐的模式。何时该用何时不该用这条规则属于重渲染优化大类rerender-前缀优先级别为MEDIUM。它不是所有组件的默认要求而是有明确适用场景。适合使用的情况计算确实昂贵如解析 Markdown、生成 SVG/图表、计算头像 ID 等且并非每次渲染都需要存在提前 return 的分支如 loading、empty、error 状态这些状态下昂贵计算完全多余组件经常因无关状态如父组件的 loading 切换而重渲染且 props 变化频率低列表/表格中的每一行props 是稳定的原始类型。不适合的情况计算本身很廉价memo 化带来的收益不如其认知成本props 每次渲染都必然变化此时memo的比较本身就是浪费代码可读性受损的程度超过优化收益。与 React Compiler 的关系规则文档特别指出如果你的项目启用了 React Compiler手动使用memo()和useMemo()就不再必要编译器会自动优化重渲染。React Compiler 会在构建时自动分析组件插入必要的 memo 化逻辑因此手写的memo/useMemo是冗余的。在启用编译器的项目中应将精力集中在组件结构上——比如把昂贵计算提取到独立组件、把提前 return 分支上移让编译器更好地发挥自动优化的作用。小结手段解决的问题适用位置memo()不必要的重渲染独立子组件useMemo()不必要的重复计算组件内部提前 return不需要时完全不计算父组件渲染逻辑提取为 memoized 组件的实质是利用 React 的组件树结构本身来做短路计算让昂贵计算只存在于它真正被需要的分支里再叠加memo/useMemo防止重复工作。这个模式在 Polar 这样的中大型 React 应用中如 dashboard 分析页、列表渲染、Markdown 渲染等场景是实用且被实际采用的优化手段。如果你的项目还没有启用 React Compiler这条规则值得作为日常编码习惯如果已启用则只需关注组件拆分与分支结构。扩展阅读本规则属于 Vercel 的 React/Next.js 性能优化规则集仓库中还包含更多相关主题可参考.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md— 不要在回调中订阅状态.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md— 使用原始类型依赖.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md— 订阅派生布尔值而非原始值.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md— 使用函数式 setState.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md— 使用惰性状态初始化.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md— 使用 startTransition以及规则集的完整汇总文档 .agents/skills/vercel-react-best-practices/AGENTS.md 和规则索引 .agents/skills/vercel-react-best-practices/SKILL.md。【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表