open-agents 中的 React 性能规则实践:窄化 useEffect 依赖项,最小化副作用重跑(rerender-dependencies)
发布时间:2026/9/17 5:39:57 作者:尧图编辑部 阅读量:1,286
)
open-agents 中的 React 性能规则实践窄化 useEffect 依赖项最小化副作用重跑rerender-dependencies【免费下载链接】open-agentsAn open source template for building cloud agents.项目地址: https://gitcode.com/GitHub_Trending/op/open-agentsopen-agents 仓库内置了一份面向 AI Agent 与 LLM 的 Vercel React 最佳实践规则库其中rerender-dependencies规则规定了如何为useEffect声明依赖项把副作用的重跑次数压到最低。读完本文你会理解该规则在整套 React/Next.js 性能规则体系中的定位、依赖比较的底层机制掌握对象依赖、派生条件依赖的三类典型改写方式以及它与其他重渲染优化规则如何协同、适用边界在哪里从而在 Next.js 客户端组件中稳定写出「只在真正需要的值变化时才执行」的副作用。1. 规则定位rerender 类别中的一环该规则的原始文档位于 .agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md其 frontmatter 元数据为title:Narrow Effect Dependencies窄化 Effect 依赖impact:LOW影响等级低impactDescription:minimizes effect re-runs最小化 effect 重跑tags:rerender, useEffect, dependencies, optimization这份规则是技能 .agents/skills/vercel-react-best-practices/SKILL.md 所述「58 条规则、8 个类别」体系的一部分。技能定义了如下按优先级排序的类别框架引自 SKILL.md优先级类别影响前缀1消除瀑布式请求Eliminating WaterfallsCRITICALasync-2包体积优化Bundle Size OptimizationCRITICALbundle-3服务端性能Server-Side PerformanceHIGHserver-4客户端数据获取Client-Side Data FetchingMEDIUM-HIGHclient-5重渲染优化Re-render OptimizationMEDIUMrerender-6渲染性能Rendering PerformanceMEDIUMrendering-7JavaScript 性能JavaScript PerformanceLOW-MEDIUMjs-8高级模式Advanced PatternsLOWadvanced-从这套体系可以看出两点其一本规则属于第 5 类「重渲染优化」rerender-前缀整体 MEDIUM 档而单条规则自身被进一步标记为 LOW——这意味着它不是性能优化的「头号杀手锏」那是瀑布请求与包体积而是一类「少跑几次副作用」的增量收益规则其二规则文件通过文件名前缀自动归入对应章节构建时会按标题排序并生成编号。编译后的完整文档见 .agents/skills/vercel-react-best-practices/AGENTS.md该规则对应其中的5.6 节「Narrow Effect Dependencies」。规则库的构建与校验流程在 README 中说明每条规则是一个独立的rules/前缀-描述.md文件通过构建脚本编译生成 AGENTS.md 与测试用例文件。对使用者来说这意味着每条规则都具备独立可读性与可检索性本规则即可单独作为评审检查项使用。2. 核心原则用原始值而非对象做依赖规则原文一句话概括Specify primitive dependencies instead of objects to minimize effect re-runs. 用原始值类型的依赖替代对象依赖以最小化 effect 重跑。2.1 为什么[user]会「误触发」React 对useEffect的依赖数组做浅比较每一项与上一次渲染的对应项用Object.is相等性比较可推断为字符串、数字、布尔值、null/undefined 等原始值比较结果对象与数组则比较引用地址。由此产生规则针对的行为差异// 不正确user 是对象。任何一次 user 引用变化哪怕只有 email 字段变了 // 都会让依赖比较失败effect 整体重跑 useEffect(() { console.log(user.id) }, [user])// 正确依赖原始值 user.id。只有 id 真正改变时才重跑 useEffect(() { console.log(user.id) }, [user.id])错误示例中effect 体内实际只读取了user.id一个字段却订阅了整个user对象。当上游数据把user替换为一个新对象例如接口刷新、store 更新而id并未变化时[user]版本的 effect 会白白重跑[user.id]版本则静默跳过。这正是「narrow窄化」的含义依赖声明的范围应当收缩到 effect 体真正消费的字段为止。2.2 派生条件先算布尔值再依赖布尔值原文给出的第二个例子针对的是「连续值 → 布尔语义」的场景// 不正确width 是连续变化的原始值。宽度从 767 → 766 → 765…… // 每一次变化都会触发 effect 重跑但业务语义是否移动端从未改变 useEffect(() { if (width 768) { enableMobileMode() } }, [width]) // 正确在渲染期先派生出布尔值 isMobile // 只有布尔值发生 0↔1 跃迁时 effect 才重跑 const isMobile width 768 useEffect(() { if (isMobile) { enableMobileMode() } }, [isMobile])这个示例值得单独展开width本身已经是原始值依赖它并不违反「原始值依赖」的字面要求但业务条件只关心「是否小于 768」这个二值结论。把比较运算上移到渲染期const isMobile width 768让依赖项退化为布尔值后effect 的重跑次数从「每个像素变化一次」降为「跨过阈值一次」。这也解释了文档小标题「For derived state, compute outside effect」——派生计算必须放在 effect 之外即渲染函数体中否则依赖数组里放不进去尚未计算的中间结果。3. 与其他 rerender 规则的分工把本规则放进同族规则里看边界更清晰以下均出自同一技能目录的rules/文件规则文件解决的问题与本规则的关系rerender-derived-state.md组件级订阅派生布尔状态而非连续值如用useMediaQuery((max-width: 767px))替代useWindowWidth()同一思想在组件重渲染层面的应用本规则是其在effect 依赖层面的应用rerender-derived-state-no-effect.md能渲染期算出的值不要存 state、不要用 effect 同步上游决策若派生值根本不需要触发副作用应优先在渲染期直接派生连 effect 都不留rerender-defer-reads.md仅在回调里读取的 state如useSearchParams不要订阅与本规则互补本规则解决「依赖谁」它解决「要不要依赖」advanced-use-latest.md用useLatest获得稳定回调引用当 effect 必须读取频繁变化的值但不想因此重跑时配合本规则使用可以这样归纳三者的决策顺序能不能不写 effect若值只是派生展示按rerender-derived-state-no-effect在渲染期直接计算能不能不订阅若只在事件回调里读取按rerender-defer-reads在回调内按需读取必须订阅时依赖谁按本规则把依赖收窄为原始值 / 派生布尔值。4. 边界与陷阱窄化不是漏写窄化依赖项有一个前提effect 体读取的每一个响应式值都必须被某个依赖覆盖否则就会读到过期闭包值。原文档聚焦在「多订阅」问题以下是实践中必须同时守住的边界基于 React 依赖比较机制的一般性约束供评审时对照检查禁止用「窄化」为由删掉真实读取的依赖。若 effect 体同时读取user.id与user.name依赖就必须是[user.id, user.name]只写[user.id]会让name变成陈旧值触发 ESLintreact-hooks/exhaustive-deps告警。对象依赖并非总是错误。若 effect 确实消费对象的多个字段声明整个对象是诚实且安全的多跑几次副作用但永远读到最新值。本规则的收益只出现在「effect 只消费对象的一小部分字段」的场景。引用不稳定的对象依赖是重灾区。[props.filters]每次渲染新构造的对象/数组会让 effect 每次渲染都重跑。处理方式取决于意图要么按本规则拆出真正用到的原始字段要么在渲染期用useMemo稳定引用要么把交互逻辑移出 effect参见同族规则 rerender-move-effect-to-event.md——「交互触发的副作用应放事件处理器」。派生值必须在渲染期计算。const isMobile width 768这类语句放在组件函数体内、useEffect调用之前放进 effect 体内则依赖数组无从引用问题依旧。与 React Compiler 的关系编译文档中明确提示若项目启用了 React Compilermemo()/useMemo()之类的手工记忆化不再必要编译器会自动处理重渲染优化。对 effect 依赖Compiler 不改变「依赖数组决定 effect 何时重跑」这一机制但会缓解其中依赖的引用稳定性问题——从编译文档的口径看该提示主要针对渲染层面的手工记忆化对 effect 依赖声明仍需开发者自行收窄。5. 在 open-agents 中的应用语境open-agents 是一个基于 Next.js App Router 的 monorepo工作区见 AGENTS.mdWeb 应用位于apps/web另含packages/agent、packages/sandbox等包。其前端包含大量带客户端状态与副作用的组件会话列表、聊天界面、设置页等如 apps/web/components、apps/web/hooks 目录数据获取普遍走 SWR 封装见 apps/web/lib/swr.ts因此「客户端组件中 effect 因对象依赖频繁重跑」是真实存在的代码形态本规则对该类代码的评审与生成具有直接约束力。在该仓库落地这条规则时建议结合其既有工程约定代码风格仓库要求双引号、2 空格缩进、Bun 工具链AGENTS.md 的 Code Style 一节。改写依赖数组时保持既有风格并通过bun run cilint typecheck tests与bun run check验证。文件组织仓库要求「每个文件聚焦单一职责大组件的新逻辑提取到同目录 hooks/子组件」。这与本规则形成呼应——当一个组件的 effect 依赖越来越杂往往是该把派生值与副作用拆进独立 hook 的信号拆出后每个 effect 的依赖面更小也更容易做到「依赖即原始值」。技能触发场景SKILL.md 规定该技能在「编写新 React 组件、实现数据获取、性能评审、重构现有代码、优化包体积或加载时间」时应用。即在 Agent 生成或评审 open-agents 中任何useEffect代码时应检查依赖数组里是否存在 effect 体并未消费的整个对象连续值依赖能否替换为渲染期派生的布尔值。6. 评审检查清单对任意一段包含useEffect的 Next.js/React 代码可按以下顺序执行本规则的检查列出 effect 体内实际读取的每个响应式值将依赖数组中每个「对象/数组」依赖展开为其被消费的字段改为原始值依赖检查依赖中是否存在「连续值 → 业务条件」的情形若是在渲染期派生布尔值并改为依赖布尔值反向检查effect 体读取的每个响应式值是否都在依赖数组中防过期闭包若依赖项是每次渲染新建的对象/数组引用判断是拆字段、稳定引用还是把逻辑移出 effect转入事件处理器。规则本身标注的影响等级为 LOW属于「少跑几次 effect」的增量优化但它与同族的 rerender-derived-state.md、rerender-derived-state-no-effect.md、rerender-defer-reads.md 一起构成完整的「订阅最小化」决策链——先问要不要 effect、再问要不要订阅、最后问依赖该多窄——在 open-agents 这类含大量客户端交互状态的 Next.js 应用中是控制副作用重跑频率的直接手段。完整规则集及其优先级框架见 SKILL.md 与编译文档 AGENTS.md5.6 节规则文件的组织与构建方式见 README。【免费下载链接】open-agentsAn open source template for building cloud agents.项目地址: https://gitcode.com/GitHub_Trending/op/open-agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考