
OpenMontage React 最佳实践渲染期计算派生状态——停止用 useEffect 同步 state直接从 props/state 派生【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage本文为 OpenMontage 技能库vercel-react-best-practices中第 5 章「Re-render Optimization重渲染优化」的首条规则Calculate Derived State During Rendering渲染期计算派生状态的完整解读。OpenMontage 作为 agentic 视频生产系统在.agents/skills/下维护了 700 余个面向 AI 编码助手的「生产知识文件」本文档rerender-derived-state-no-effect.md就是其中用于指导 React/Next.js 代码生成与评审的规则之一。读完后你将掌握该规则在技能体系中的位置与编译流程、用 effect 同步派生值为何必然引入多余渲染与状态漂移、以及如何在实际组件包括本仓库 Remotion composer 代码中正确地在渲染期派生状态。规则在技能体系中的位置该规则文件位于 .agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md其 YAML frontmatter 定义了规则在技能库中的全部元数据--- title: Calculate Derived State During Rendering impact: MEDIUM impactDescription: avoids redundant renders and state drift tags: rerender, derived-state, useEffect, state ---这几个字段并非摆设它们直接驱动技能库的构建与检索文件名前缀决定所属章节按 README.md 的命名约定rerender-前缀对应第 5 章「Re-render Optimization」。各章节的前缀、影响级别与描述统一定义在 rules/_sections.md 中——第 5 章标注为MEDIUM级影响目标是「减少不必要的重渲染避免浪费计算、提升 UI 响应性」。章节内按标题字母序自动编号构建时会把所有规则按 title 排序并自动生成 ID。该规则在编译产物 AGENTS.md 中被编译为5.1 Calculate Derived State During Rendering「5. Re-render Optimization」章节的第一条。影响级别语义impact: MEDIUM对应 README 中定义的中档收益「Moderate performance improvements」impactDescriptionavoids redundant renders and state drift则说明收益来源是消除冗余渲染和状态漂移。tags 用于检索rerender, derived-state, useEffect, state四个标签让 Agent 在按主题检索规则时能快速命中。整个技能由 SKILL.md 作为可加载入口它声明了 65 条规则、8 个类别并明确该技能「应在编写、评审或重构 React/Next.js 代码确保最优性能模式时」被触发本规则在其中被登记为rerender-derived-state-no-effect- Derive state during render, not effects规则文件本身遵循 rules/_template.md 定义的结构一句话原则 →Incorrect错误示例→Correct正确示例→ 补充说明与引用。技能库支持pnpm build将规则编译为 AGENTS.md、pnpm validate校验规则文件与pnpm extract-tests抽取 LLM 评估用例三个脚本在本仓库中只需查看编译后的 AGENTS.md 即可获得全部规则的长文形式。核心原则能在渲染期算出来的就不要存进 state规则的完整表述只有两句话但覆盖面很广If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.即凡是能由当前 props/state 计算出来的值都不要存进 state也不要用 effect 去更新它直接在渲染期派生以避免多余渲染与状态漂移。也不要在 effect 中仅仅响应 prop 变化就 setState应优先用派生值或 keyed reset用 key 强制重置。这里点出了两个独立的反模式派生值存 state把fullName这类纯计算结果提升为独立状态effect 响应 propuseEffect的依赖里只有 prop/state效果是setX(prop 的函数)——这是 React 官方文档所称「You Might Not Need an Effect」的典型场景也是该规则文末引用的参考主题。为什么错误写法会「双渲染」且「漂移」规则给出的反例代码function Form() { const [firstName, setFirstName] useState(First) const [lastName, setLastName] useState(Last) const [fullName, setFullName] useState() useEffect(() { setFullName(firstName lastName) }, [firstName, lastName]) return p{fullName}/p }对照 React 的「render → commit → 运行 effect」生命周期一次firstName变更的实际代价是第一次渲染firstName已更新但fullName仍是旧值effect 还没跑——用户会短暂看到过期内容effect 在提交后触发setFullName(...)安排第二次渲染此时fullName才追上。也就是说每次 prop 变化都付出两次渲染且中间帧展示的永远是滞后一拍的派生值。初始状态下问题更直观fullName初始为首屏直接渲染一个空字符串直到挂载后的 effect 把它补上——这就是规则标题里的state drift状态漂移fullName作为一个独立状态源与它「本该等于」的firstName lastName在时间轴上永久差一个 effect 的时延任何 prop 的快速连续变更、StrictMode 下的双调用、或多个此类 effect 的先后顺序都会放大这种不一致。正确写法则只有五行核心逻辑function Form() { const [firstName, setFirstName] useState(First) const [lastName, setLastName] useState(Last) const fullName firstName lastName return p{fullName}/p }fullName不再拥有独立生命周期它每次渲染都从当前 props/state 即时算出渲染次数减半、漂移在结构上不可能发生代码还少了一个状态、一个 setter 和一个 effect 的维护面。判定清单什么时候该派生什么时候必须用 state规则给出的判据是「能否由当前 props/state 计算出来」。落到日常代码可以按下面的清单自测场景结论由现有 props/state 纯函数可得拼接、格式化、过滤、区间计算渲染期直接派生effect 里唯一的逻辑就是setX(基于依赖的表达式)几乎总是应该改为渲染期派生用户输入、累积历史、一次性外部数据请求结果、订阅源合法 state不在本规则管辖内prop 变化后需要把子组件的内部状态「归零」用key{...}强制 remount规则所称 keyed reset而不是在 effect 里 setState派生计算本身昂贵仍属于派生值应配合同章节的 memo/useMemo 类规则如rerender-memo缓存而不是退回「state effect」其中keyed reset值得单独强调很多人写useEffect(() setDraft(props.seed), [props.seed])是想实现「seed 变了草稿就重置」。规则给出的正解是给子组件传key{props.seed}让 React 以全新挂载代替状态修补——既避免 effect 时序问题也天然清掉子树下所有内部状态。仓库实例Remotion composer 中的时间轴派生本仓库的视频合成层remotion-composer/是一个 React TypeScript 代码库恰好大量实践了本规则。以 EndTag.tsx 为例它是片尾卡组件所有时间轴节点都是在渲染函数体内从 props 直接派生的const frame useCurrentFrame() const { fps } useVideoConfig() // Timing in frames const fadeInFrames Math.round(fadeInSeconds * fps) const holdFrames Math.round(holdSeconds * fps) const fadeOutFrames Math.round(fadeOutSeconds * fps) // Full opacity envelope: fade in - hold - fade out const fadeInEnd fadeInFrames const fadeOutStart fadeInEnd holdFrames const fadeOutEnd fadeOutStart fadeOutFramesfadeInFrames、fadeOutStart、fadeOutEnd完全由 propsfadeInSeconds等与视频配置fps决定组件没有为它们建任何 state也没有任何 effect 去「同步」——这正是规则 Correct 示例的规模化版本。同文件里下划线动画的underlineStartFrame fadeInEnd Math.round(0.2 * fps)也是从派生值再派生链条依然全部落在渲染期。类似地AnimeScene.tsx 中的const imageCount images.length也是渲染期直接从 props 派生的计数。从源码结构看Remotion 组件的每帧渲染是逐帧重算的过程「无 effect、无 state 同步、全部派生」的写法在这里不仅是性能优化更是保证帧输出确定性的正确性手段——这解释了该仓库在把 agent 生成/评审 React 组件代码时引入这套 Vercel 规则库的现实背景。与同章节规则的关系本规则不是孤立的一条它是rerender-前缀 15 条规则组成的体系中的一环见 SKILL.md 第 5 节清单。与它最贴近的两条rerender-derived-state.mdSubscribe to Derived State讲的是订阅层面——订阅派生后的布尔值如useMediaQuery((max-width: 767px))得到的isMobile而不是连续原始值如窗口宽度让重渲染只在布尔翻转时发生。一条管「渲染期怎么算」一条管「订阅什么值」方向互补。rerender-memo.md 等缓存类规则当派生计算昂贵时用记忆化而不是状态同步来降频。三条合在一起构成完整的派生值治理渲染期派生为默认昂贵计算加 memo订阅收窄到布尔。小结Calculate Derived State During Rendering这条 MEDIUM 级规则用最小篇幅钉死了一条高频反模式「value f(props, state)」的结果不是状态不该有useStateuseEffect的同步链路。遵循它每次 prop 变更的渲染次数从两次降为一次状态漂移被结构消除遇到「prop 变化重置内部状态」的需求改用 keyed reset。在 OpenMontage 这类以 AI 编码助手为核心的生产系统里这条以固定模板frontmatter Incorrect/Correct 对照 参考编写的规则文件可以被pnpm build编译进 AGENTS.md 供 Agent 在生成与评审 React 代码时逐条执行也让本仓库 remotion-composer/src/components/ 下「渲染期全派生」的组件写法有了明确的规范出处。【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考