)
Polar 前端性能实践把交互逻辑移入事件处理器Put Interaction Logic in Event Handlers【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar导读本文讲解 Polar 前端工程clients/apps/web所沉淀的 Vercel React 最佳实践规则之一由用户明确动作提交、点击、拖拽触发的副作用应直接在事件处理器中执行而不是把动作建模成状态 useEffect。读完本文你将理解 useEffect 重复执行副作用的底层原理、两种写法的风险对比并掌握一套可复用的判定标准与配套优化手段收窄依赖、派生状态、ref 防重入、useEffectEvent让表单、弹窗、列表等高频交互组件既少渲染又无重复副作用。规则背景它来自哪里优先级如何这条规则位于仓库中的技能包目录规则原文clients/apps/web/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md技能包总览clients/apps/web/.agents/skills/vercel-react-best-practices/SKILL.md这是一套由 Vercel 工程团队维护、面向 React / Next.js 的性能优化指南共 64 条规则按影响程度分为 8 个类别。rerender-前缀对应第 5 类Re-render Optimization重渲染优化影响等级为MEDIUM中等其 impactDescription 明确写道avoids effect re-runs and duplicate side effects避免 effect 重复执行与副作用重复。同类的相邻规则还有rerender-dependencies.md收窄 effect 依赖rerender-derived-state-no-effect.md渲染期计算派生状态advanced-event-handler-refs.md把事件处理器存进 ref使用 useEffectEvent在编译后的完整文档 AGENTS.md 中本规则编号为5.8 Put Interaction Logic in Event Handlers。也就是说它属于中等收益、高频出现的一类问题不修复不致命但修复后对渲染次数与副作用正确性有直接、可感知的改善非常适合在代码评审和 AI 辅助重构时自动套用。核心问题动作被建模成状态 effect的两大隐患规则原文给出的错误写法如下function Form() { const [submitted, setSubmitted] useState(false) const theme useContext(ThemeContext) useEffect(() { if (submitted) { post(/api/register) showToast(Registered, theme) } }, [submitted, theme]) return button onClick{() setSubmitted(true)}Submit/button }这段代码把用户点击提交这一一次性动作转换成了submitted状态从 false 变为 true这一状态变更再用 effect 监听状态变化去执行副作用。它至少带来两个问题1. effect 会在无关变更时重复执行useEffect的依赖数组[submitted, theme]意味着只要submitted或theme中任何一个值变化effect 就会重新运行。theme来自ThemeContext它属于全局共享上下文——主题切换、Context 提供者重渲染导致的新引用、甚至父组件任何一次导致 context value 变化的重渲染都会让这个 effect 再次触发post(/api/register)。更隐蔽的是即使submitted已经是true只要后续theme一变if (submitted)依然成立副作用会被再次执行。用户只点击了一次提交网络请求却可能发出多次。这正是 impactDescription 中 duplicate side effects副作用重复的典型场景。2. 动作语义被错误地持久化了submitted是一个布尔状态一旦置为true就不会自动复位。它把发生过的动作变成了一直存在的状态副作用从应该执行一次退化为只要依赖变化就检查一次。这类状态本质上是不该存在于 state 中的瞬态信号与 rerender-derived-state-no-effect.md 中不要为了响应状态变化而在 effect 里设置 state的教训同源凡是从既有 props/state/事件能直接得出的东西都不应再引入一套状态 effect 的间接链路。另外需要说明在 React 开发模式StrictMode下 effect 会被刻意地挂载→卸载→再挂载这会让上述错误写法在开发阶段就暴露出重复提交问题但生产环境的重复触发依赖变化导致则更隐蔽需要靠依赖收窄或直接移入事件处理器来根治。正确做法直接在事件处理器里执行规则给出的正确写法function Form() { const theme useContext(ThemeContext) function handleSubmit() { post(/api/register) showToast(Registered, theme) } return button onClick{handleSubmit}Submit/button }核心变化删除了submitted状态不再用状态表达动作post与showToast被移入handleSubmit仅在用户点击时同步执行一次theme虽然仍在读取但它只参与这次点击发生时的取值不会因为 context 后续变化而触发任何副作用重放。由于事件处理器天然只跑一次对应一次用户交互重复提交与无关重渲染引发的副作用都从机制上被消除。需要注意的是theme仍会被组件读取这本身不构成多余的重渲染——规则关注的是副作用不要因依赖变化而重放而读取 context 值做渲染是正常的 React 数据流。判定标准什么代码该移进事件处理器规则原文引用 React 官方文档的判定问题Should this code move to an event handler?这段代码应该移到事件处理器吗。结合 SKILL.md 的触发场景编写新组件、评审代码、重构性能问题可以归纳出以下实操判定标准场景应如何处理副作用由明确的用户动作触发submit、click、drag、keydown、scroll 到底直接放进事件处理器副作用需要与外部系统同步订阅、轮询、WebSocket、路由变化保留在 useEffect但要收窄依赖需要在渲染期根据 props/state 计算值如 fullName渲染期直接派生不要用 state effect需要在 effect 回调里使用最新的处理器用 ref 包装或用useEffectEvent见下文核心直觉是useEffect 的职责是与外部系统同步而不是执行用户操作。一次性的、由交互直接引发的副作用发请求、弹 toast、写 localStorage、跳转都不应该被建模为状态变化后被 effect 观察到因为它们本质上不是状态。判定标准的补充effect 中如果有必须用最新值的处理器怎么办有时副作用确实需要在 effect 中执行例如监听全局事件但又希望订阅关系不随每次渲染重建。此时不应退回state effect建模而应使用配套规则 advanced-event-handler-refs.md 中的手法——把处理器存入 ref保持订阅稳定function useWindowEvent(event: string, handler: (e) void) { const handlerRef useRef(handler) useEffect(() { handlerRef.current handler }, [handler]) useEffect(() { const listener (e) handlerRef.current(e) window.addEventListener(event, listener) return () window.removeEventListener(event, listener) }, [event]) }在最新版 React 中也可以直接用useEffectEvent获得稳定引用import { useEffectEvent } from react function useWindowEvent(event: string, handler: (e) void) { const onEvent useEffectEvent(handler) useEffect(() { window.addEventListener(event, onEvent) return () window.removeEventListener(event, onEvent) }, [event]) }这两段代码展示了事件处理器归属的边界由用户动作直接触发的逻辑进事件处理器被 effect 订阅的全局事件则用 ref / useEffectEvent 保持订阅稳定。它们与本文规则互为补充共同覆盖了交互逻辑放哪的完整问题空间。仓库实证Polar 中事件处理器模式的真实用法规则并非纸上谈兵。在 Polar 的前端代码中可以找到大量交互逻辑放在事件处理器的实例以邮箱验证码登录页 clients/apps/web/src/app/(main)/auth/email-otp/VerifyPage.tsx/auth/email-otp/VerifyPage.tsx) 为例const [loading, setLoading] useState(false) const submittingRef useRef(false) const onSubmit: SubmitHandler{ code: string } async ({ code }) { if (submittingRef.current) return // 防止重复提交 submittingRef.current true setLoading(true) try { const { error } await emailOTPVerify.mutateAsync(code) if (error) { if (isValidationError(error.detail)) { setValidationErrors(error.detail, setError) } else if (error.detail) { setError(code, { message: error.detail }) } return } window.location.href ${CONFIG.FRONTEND_BASE_URL}/auth } catch { setError(code, { message: An unexpected error occurred. Please try again., }) } finally { setLoading(false) submittingRef.current false } }这段代码完全符合本文规则的三个要点验证码校验、错误回填、登录成功跳转全部在onSubmit处理器内完成没有引入任何验证状态 effect 监听的中间层用useRef维护submittingRef标志位防重入且不触发重渲染在 effect 或依赖变化下也不会重放请求页面只保留一个与 UI 直接相关的loading状态其更新也在处理器内成对出现setLoading(true)/finally中复位。从源码结构看仓库中同一模式的表单组件如 VerifyPage.tsx/auth/email-otp/VerifyPage.tsx)、RequestPage.tsx/[organization]/portal/request/RequestPage.tsx) 等普遍遵循提交逻辑在 submit handler、UI 状态用 state、防重入用 ref的分工这正是本规则落地后的代码形态。相关规则联动一条完整的去 effect 化检查清单本文规则很少单独出现实践中常与 Re-render Optimization 类目下的其他规则组合使用。给出一份组合自查清单[本规则] 动作副作用由用户动作触发 → 移入事件处理器rerender-dependencies.md 依赖收窄必须留在 effect 中的副作用 → 用原始值/布尔派生值代替对象依赖避免无关变化触发如[user.id]代替[user]rerender-derived-state-no-effect.md 派生状态能在渲染期算出的值如fullName firstName lastName→ 渲染期直接计算绝不在 effect 里setStateadvanced-event-handler-refs.md 稳定订阅effect 中需要最新回调 → 用 ref 或useEffectEvent包装。按这条链路检查大多数effect 滥用都能被收敛到三种正确形态之一事件处理器、渲染期派生、真正的外部系统同步。小结触发规则由 submit / click / drag 等明确用户动作触发的副作用直接放进事件处理器不要建模成状态 effect。核心收益避免 effect 在无关依赖变化时重复执行从机制上杜绝重复请求、重复 toast 等重复副作用影响等级 MEDIUM。配套手段需要保留在 effect 里的逻辑配合收窄依赖原始值依赖、渲染期派生状态、ref / useEffectEvent 稳定订阅使用。仓库依据规则原文见 rerender-move-effect-to-event.md编译版见 AGENTS.md 的 5.8 节真实落地示例见 VerifyPage.tsx/auth/email-otp/VerifyPage.tsx)。把交互逻辑放回事件处理器是 React 组件从响应式正确走向性能与行为都正确的第一步也是 SKILL.md 中 Re-render Optimization 类别里性价比最高、最适合自动化评审直接套用的规则之一。【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考