ARTICLE DETAIL

资讯详情

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

深入解析 React Effect 反模式:将交互逻辑放入事件处理器(Vercel React Best Practices 实战指南)

深入解析 React Effect 反模式:将交互逻辑放入事件处理器(Vercel React Best Practices 实战指南) 深入解析 React Effect 反模式将交互逻辑放入事件处理器Vercel React Best Practices 实战指南【免费下载链接】phoenixAI Observability Evaluation项目地址: https://gitcode.com/gh_mirrors/phoenix13/phoenix本文源自当前仓库 Vercel React Best Practices 技能库中的核心规则 rerender-move-effect-to-event.md。该技能库由 Vercel Engineering 维护收录了 70 余条面向 React/Next.js 的性能优化规则按影响度分为 8 大类本规则属于第 5 类Re-render Optimization重渲染优化影响度为 MEDIUM。规则速览什么情况下必须把副作用搬进事件处理器当一个副作用side effect是由某个明确的用户操作提交表单、点击按钮、拖拽元素、键盘输入等触发时应当在该操作对应的事件处理器event handler中直接执行它而不要把用户执行了操作建模为一个 state 再交给useEffect去响应。后者会让 effect 在无关状态变更时被反复触发并可能导致同一个副作用被重复执行。这是 React 官方移除 effect 依赖指南中的一个经典判例Should this code move to an event handler?这段代码是否应该移到事件处理器中。判定标准简单直接如果某段副作用代码只应当响应用户的一次具体操作而运行一次它就是事件处理器该做的事而不是 effect 该做的事。反模式剖析用 state effect 建模一次提交下面是一个典型的错误写法——把是否提交作为布尔 state再让 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 }这段代码存在两个问题依赖数组使 effect 在无关变更时重复运行effect 的依赖是[submitted, theme]。theme来自ThemeContext它并不代表提交这个动作但只要主题上下文发生变化effect 就会重新运行并再次调用post(/api/register)——用户可能被重复注册。这正是本技能库中 rerender-dependencies.md 强调的问题effect 依赖越宽越容易被无关变更重新触发。语义错位submitted这个 state 除了通知 effect 执行一次副作用之外没有其他用途它是为 effect 而存在的中间人。副作用本身并没有消费任何随时间变化的数据它只是响应一次点击。正确范式副作用直接放进事件处理器function Form() { const theme useContext(ThemeContext) function handleSubmit() { post(/api/register) showToast(Registered, theme) } return button onClick{handleSubmit}Submit/button }改写后的行为完全符合预期post(/api/register)和showToast(...)只会在用户点击按钮时执行一次组件不再维护多余的submittedstate少了一次 state 写入就少触发一次重渲染不再有依赖数组theme或其他上下文的任何变化都不会误触发副作用事件处理器总是从最新一次的渲染闭包中读取theme不存在陈旧值问题。从重渲染优化的角度看这个改写同时消除了两类开销一是去掉了多余的 state 更新与随之而来的重渲染二是让副作用与组件的渲染生命周期彻底解耦。背后的原理为什么 effect 会多跑和重跑要真正理解这条规则需要澄清 effect 的两个行为特性1. effect 可能执行多次重跑useEffect的依赖数组采用引用/值比较数组中任何一个依赖项发生变化React 就会先执行上一次 effect 的清理函数再重新执行 effect。因此任何被塞进依赖数组的无关值如theme都等于给副作用装了一个意外扳机。2. 开发环境下 effect 会被有意地双执行React 在开发模式配合StrictMode下会故意把 effect 挂载→卸载→再挂载以便暴露未正确清理的副作用。也就是说把提交注册这种不可幂等的操作放进 effect即使在依赖数组完全正确的情况下开发环境里也可能看到副作用执行两次而事件处理器永远不会被 React 以这种方式重复调用。把操作放入事件处理器是让一次用户操作 一次副作用的唯一可靠保证。3. effect 是渲染后的异步补丁事件处理器是交互的同步应答Effect 在浏览器绘制后运行用于把组件与外部系统订阅、计时器、网络请求等同步而事件处理器直接对应一次用户意图。前者适合持续存在的关系后者适合一次性动作。何时该用事件处理器、何时该保留 effect判定清单场景该用事件处理器该用 effect用户点击提交/保存/删除按钮✅ 直接在 onClick 中执行❌拖拽结束、选中变化后的即时反馈✅❌订阅外部事件源socket、storage、窗口事件❌✅ 并在清理函数中取消订阅组件挂载时加载初始数据❌✅或用数据请求库对值的变化过程做持续响应如宽度变化触发布局切换❌✅ 但要收窄依赖仅需记录一次分析/埋点事件✅❌核心判据来自本规则的标题本身这段副作用代码是否绑定在一个具体的用户动作上是——进事件处理器否它是对某个持续变化的数据流的响应——保留 effect同时遵循 rerender-dependencies.md 的收窄依赖原则// 属于持续响应保留 effect但依赖用原始值收窄 const isMobile width 768 useEffect(() { if (isMobile) { enableMobileMode() } }, [isMobile]) // 只在该布尔值翻转时触发而不是每次 width 变化与技能库中相邻规则的配合使用这条规则不是孤立的它与本技能库 Re-render 优化分类SKILL.md 第 5 节中的其他规则共同构成一套完整的去 effect 化方法论rerender-derived-state-no-effect.md如果值能从现有 props/state 计算得出就在渲染期间直接推导而不是存进 state 再靠 effect 同步。与本规则同理——能省掉的 effect 就不要创建。rerender-functional-setstate.md事件处理器内若需要基于当前 state 更新状态应使用函数式更新setItems(curr ...)避免把 state 值放进依赖数组造成闭包陈旧或回调频繁重建。rerender-dependencies.md对于确实需要保留的 effect依赖必须收窄到原始值/布尔派生值减少重跑机会。advanced-effect-event-deps.md与advanced-event-handler-refs.md在必须在 effect 内部注册回调如订阅事件源但回调要读取最新值的少数场景中使用useEffectEvent或在 ref 中保存最新 handler避免把回调函数本身放入依赖数组导致 effect 每次渲染都重订阅——这是事件处理器优先原则在 effect 边界内的延伸解法。例如监听窗口事件的 hook 若把 handler 直接放进依赖数组会在每次渲染后重新订阅改用 ref 或useEffectEvent后订阅只依赖event名称稳定且不会漏读最新 handlerimport { useEffect, useEffectEvent } from react function useWindowEvent(event: string, handler: (e: Event) void) { const onEvent useEffectEvent(handler) useEffect(() { window.addEventListener(event, onEvent) return () window.removeEventListener(event, onEvent) }, [event]) // 注意依赖数组里是 event不是 onEvent }实战自查清单在代码评审或重构时遇到useEffect可以先问自己四个问题对应规则原文件的判定要点触发源是谁如果只有用户点了某个按钮/提交了表单才会需要执行立刻把逻辑搬进事件处理器依赖数组里有没有陪跑依赖如果有与副作用无关的上下文值说明这个 effect 本就不该存在或需要像收窄[submitted, theme]那样大幅收窄这个 state 是不是只为 effect 而生如果删掉它 effect 就不需要了说明它是多余的中间人状态应一并删除开发环境双执行能否接受提交、支付、发送通知等不可幂等的操作放在 effect 里在 StrictMode 下可能被调用两次事件处理器是更安全的落点。结语把交互逻辑放进事件处理器是 React 组件开发中性价比最高的性能与正确性改进之一它同时减少了 state 数量、减少了重渲染次数、消除了依赖数组带来的意外副作用重跑并保证了一次操作只产生一次副作用的语义。在维护 React/Next.js 代码库时将这条规则与 rerender-derived-state-no-effect.md、rerender-dependencies.md、rerender-functional-setstate.md 等规则组合使用即可系统性地消除由错误 effect 用法带来的重渲染与重复副作用问题。完整规则集可在仓库 .agents/skills/vercel-react-best-practices/ 目录下查看。【免费下载链接】phoenixAI Observability Evaluation项目地址: https://gitcode.com/gh_mirrors/phoenix13/phoenix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表