ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

plate 编辑器中的 Memoized 组件抽取:用早期返回避免不必要的重渲染计算

plate 编辑器中的 Memoized 组件抽取:用早期返回避免不必要的重渲染计算 plate 编辑器中的 Memoized 组件抽取用早期返回避免不必要的重渲染计算【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文讲解 Vercel React 性能实践规则集中的一条核心规则——Extract to Memoized Components抽取为记忆化组件其核心思路是把昂贵的计算下沉到memo()包裹的子组件中从而让父组件可以通过**早期返回early return**在计算发生之前提前退出渲染。本文不仅给出规则原文的反例与正例还结合本仓库plate基于 Slate 的 React 富文本编辑器的真实源码展示React.memo在编辑器渲染管线中的落地方式并说明当项目启用 React Compiler 后手工记忆化应该如何取舍。规则出处与定位本规则来自仓库中由 Vercel Engineering 维护的 React/Next.js 性能优化指南作为 Agent/LLM 驱动的代码审查与重构 skill 存在单条规则文件rerender-memo.md规则清单与分类SKILL.md全量编译版指南AGENTS.md本文对应其中 5.6 节该 skill 共包含 69 条规则划分为 8 个按优先级排序的类别本文主题属于第 5 类Re-render Optimization重渲染优化impact: MEDIUM影响描述为 enables early returns。同类的姊妹规则还包括rerender-memo-with-default-value.md把记忆化组件的非原始类型默认参数数组、函数、对象提升为模块级常量避免每次渲染都新建实例导致memo()失效rerender-simple-expression-in-memo简单原始表达式不要套useMemorerender-no-inline-components不要在组件内部定义组件rerender-dependencieseffect 依赖尽量用原始类型。规则核心先退出再计算问题场景假设一个Profile组件需要根据user计算并渲染头像。直接的做法是在组件体内用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 }这段代码有一个容易被忽略的性能问题useMemo中的计算发生在 Profile 组件自身渲染期间。即使loading为 true、马上要返回Skeleton /computeAvatarId(user)和Avatar的创建仍然会被执行。useMemo只能帮你在依赖不变时跳过重复计算却无法阻止本次渲染中、在早期返回之前的计算发生。换句话说当loading为 true 时头像计算被白做了而它的结果根本不会被用到。正确写法抽取 memo 早期返回把头像逻辑抽成一个用memo()包裹的独立组件UserAvatar父组件先做loading判断、提前返回只有在真正需要渲染头像时才挂载UserAvatarconst 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 ) }两处关键改进早期返回前置if (loading) return Skeleton /现在发生在任何头像计算之前。只要处于加载态UserAvatar根本不会被渲染computeAvatarId完全不会执行。计算位置下移 记忆化computeAvatarId(user)被移入UserAvatar内部并由memo()useMemo双重保护。父组件Profile因其它状态例如用户收藏、主题切换等重渲染时只要传给UserAvatar的user引用没变memo()会直接复用上一次的渲染结果头像计算与Avatar的重新创建都会被跳过。与 memo 失效陷阱的关联值得注意memo()依赖的是 props 的浅比较Object.is。如果调用方每次渲染都传入一个新对象字面量UserAvatar user{{ id: x }} /记忆化会立刻失效。这与本 skill 中的姊妹规则 rerender-memo-with-default-value.md 是同一类问题memo(function UserAvatar({ onClick () {} })这种写法因为每次渲染都会新建默认函数实例导致未传onClick时记忆化依然被破坏解法是把默认值抽成模块级常量const NOOP () {}。因此在应用本规则时要一并检查传入 memo 组件的 props 是否保持引用稳定。何时适用、何时不该用memo()不是银弹它也有成本每次渲染都要做 props 浅比较。本规则特别适合以下场景计算昂贵且渲染频繁例如大型列表项、图表、代码高亮、头像/图片处理等父组件高频重渲染但子组件输入很少变化此时浅比较的收益远大于开销存在可提前返回的分支loading、空态、权限不足等场景正是本文早期返回模式的用武之地。反之以下情况不必引入子组件本身渲染开销极小一个简单的span浅比较成本可能反而更高依赖项是每次都会新建的引用对象/数组/函数memo会形同虚设项目已启用 React Compiler见下节。关于 React Compiler 的特别说明规则原文末尾有一条重要注释如果项目启用了 React Compiler手工的memo()和useMemo()就不再必要——编译器会在构建期自动分析组件并注入等价的重渲染优化。这一点在本仓库中有真实佐证应用侧 apps/www/next.config.ts 配置了reactCompiler: !isDev即生产构建开启 React Compiler、开发模式关闭构建侧 tooling/config/tsdown.config.ts 为库打包注册了babel-plugin-react-compilertarget 18根 package.json 声明了babel-plugin-react-compiler: 1.0.0依赖且大量包如 packages/core/package.json、packages/basic-nodes/package.json 等都依赖react-compiler-runtime。也就是说plate 这类同时面向库作者tsdown 打包与应用使用者Next.js 生产构建的仓库两条路径都启用了 React Compiler。因此在这些场景中规则强调的手工 memo应让位于编译器自动优化而对于未开启编译器的项目开发模式、CDN 直用的 UMD 构建、或未接入编译器的基础设施手工memo()/useMemo()仍然是有效手段。仓库源码印证React.memo 在编辑器渲染管线中的实际用法本仓库并非仅在规范文档层面提及记忆化其核心包 packages/core 的静态渲染组件 PlateStatic.tsx 中就有真实的React.memo实践。ElementStatic的写法PlateStatic.tsxexport const ElementStatic React.memo( BaseElementStatic, (prev, next) (prev.element next.element || (prev.element._memo ! undefined prev.element._memo next.element._memo)) isElementDecorationsEqual(prev.decorations, next.decorations) );这段代码演示了React.memo的高级用法——自定义比较函数引用相等优先prev.element next.element直接命中时跳过重渲染_memo标记快速路径当 element 携带稳定的_memo标识且相等时同样跳过这相当于在节点模型层面预置了内容未变的凭据装饰对比兜底只有 decorations 也相等才真正跳过避免漏掉编辑器装饰如高亮、光标选区渲染的变化。同文件稍后还有LeafStatic React.memo(BaseLeafStatic, ...)PlateStatic.tsx对叶子节点做类似的记忆化。这正对应了规则的核心语义把昂贵的渲染工作下沉到 memo 化的组件边界在编辑器这种一次输入事件就会引发大范围候选重渲染的场景里用浅比较或自定义比较把未变化的节点挡在渲染管道之外。从源码结构可以推断ElementStatic/LeafStatic面向服务端/静态渲染场景组件名含Static且接收SlateRenderElementProps风格的渲染属性说明 plate 有意将静态渲染路径与交互编辑路径分离静态路径通过 memo 化实现可预测的渲染成本。实战重构清单把本规则落到自己的 React/Next.js 代码中可按如下步骤执行定位热点找出渲染开销大、且父组件存在loading/empty/disabled等条件分支的组件抽取子组件将昂贵计算computeAvatarId之类连同其 JSX 输出移入独立子组件包裹 memo对子组件使用React.memo并审视 props 是否全部为稳定引用必要时参照rerender-memo-with-default-value把默认值提为常量前置早期返回父组件先判断分支条件并return让子组件在不需要时根本不参与渲染检查编译器若项目已启用 React Compiler如本仓库的reactCompiler: !isDev可移除冗余的手工 memo交由编译器处理验证收益用 React DevTools Profiler 对比重构前后的渲染次数与耗时确认浅比较开销小于省下的计算量。小结Extract to Memoized Components的本质是把计算和是否渲染解耦让父组件通过早期返回决定要不要渲染让 memo 化子组件决定要不要重新计算。这一模式在编辑器、表格、虚拟列表等高频渲染场景中尤其有价值。本仓库 PlateStatic.tsx 中带自定义比较函数的React.memo用法是它在生产级代码中的直接体现同时请记住规则最后的注脚——启用 React Compiler 时把这件事交给编译器更省心。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表