
Refine 框架实践React useEffect 清理函数详解与源码级应用【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine导读useEffect清理函数是 React 组件中防止内存泄漏、避免副作用残留的关键机制。本篇以 React 官方语义为基础系统讲解副作用side effects的分类、清理函数的工作原理以及它适用于fetch 请求、定时器、事件监听器、WebSocket 订阅等典型场景的完整写法并结合 RefineReact 内部工具/管理后台框架仓库中的真实源码如useLoadingOvertime、useSubscription等 hooks展示这套清理模式在生产级框架内部是如何落地的。读完本文你能独立完成带清理逻辑的useEffect编写并理解 Refine 等框架如何依赖该模式保证订阅、计时器与缓存失效的安全回收。什么是“副作用”side effectsuseEffecthook 是 React 中非常常用的 hook它综合了类组件时代componentDidMount、componentDidUpdate、componentWillUnmount三个生命周期方法的功能帮助开发者在组件中处理各类副作用side effects。所谓副作用指的是会影响正在执行的函数之外的某个事物的操作。在 React 中当组件影响到 React 渲染范围之外的东西时就产生了副作用。典型例子包括从远程服务器获取数据API 请求读写本地存储localStorage 等配置事件监听器addEventListener建立订阅WebSocket、实时频道订阅等。这些副作用也可能发生在按钮被点击、表单被提交、组件挂载与卸载等时机上。所有的这些活动都可能影响应用的状态如果处理不当就会引发内存泄漏。为什么要清理“副作用”在 React 中清理副作用本质上是移除那些不再需要的副作用从而避免内存泄漏——即程序或应用试图执行不再必要的操作或更新一个已不存在组件已卸载的状态内存位置。以 Refine 框架为例其管理后台通常长时间运行、页面与资源resource频繁切换。如果列表页的实时订阅在离开页面后仍在后台推送事件或在已卸载组件上执行setState不仅浪费资源还会触发浏览器警告与难以排查的 bug。因此清理函数是 React 应用性能与稳定性的重要保障。什么是 useEffect 清理函数useEffect清理函数是指在useEffecthook 的回调中返回一个函数。React 会在两种时机调用它组件卸载unmount之前依赖项变化导致 effect重新执行之前先清理旧的 effect再执行新的 effect。基础形式如下import { useEffect } from react; useEffect(() { // Your effect副作用建立连接、启动定时器、注册监听…… return () { // Cleanup拆除副作用断开连接、清除定时器、移除监听 }; }, []);可以把它理解为建立 → 拆除的成对操作effect 回调负责建立返回的函数负责拆除两者一一对应。清理函数解决了什么问题清理函数允许开发者在组件卸载或依赖变化之前停止那些不再需要执行的副作用。文档给出的经典例子某个组件在setTimeout中执行某个动作每次组件渲染都会启动这个计时器。当用户导航离开该组件时组件不再渲染计时器本应被丢弃但它仍在后台运行。这时只需在清理函数中加一个clearTimeout即可在组件卸载时清掉该计时器。这一模式在 Refine 源码中有多处印证例如 useLoadingOvertime hook —— 它负责在加载时间超过阈值时回调并记录已加载时长useEffect(() { let intervalFn: ReturnTypetypeof setInterval; if (enabled isLoading) { intervalFn setInterval(() { // 递增已加载时长 setElapsedTime((prevElapsedTime) { if (prevElapsedTime undefined) { return interval; } return prevElapsedTime interval; }); }, interval); } return () { // 卸载或依赖变化时清除定时器并重置状态 if (typeof intervalFn ! undefined) { clearInterval(intervalFn); } setElapsedTime(undefined); }; }, [isLoading, interval, enabled]);注意这里的清理函数做了两件事clearInterval停止计时以及把elapsedTime状态复位为undefined。由于依赖数组是[isLoading, interval, enabled]当加载结束isLoading变为false时清理函数同样会被触发——这正是依赖变化时先清理再重建机制的体现。何时使用清理函数五大典型场景以下场景都会促使你必须使用useEffect清理函数。1. Fetch 请求AbortController 中止请求在组件中发起API请求时必须考虑在组件卸载或重新渲染时中止请求的机制。可以使用原生fetch的AbortController或者在 Axios 客户端下使用其AbortController。用法要点用AbortController()构造函数创建控制器发起请求时把signal作为请求 options 的一部分传入将控制器与请求关联起来之后可以随时通过AbortController.abort()取消请求把controller.abort()放进useEffect清理函数确保组件卸载/重新渲染时请求被中止。Fetch 示例useEffect(() { // 创建 abort controller let controller new AbortController(); (async () { try { const response await fetch(APIEndpoint, { // 将 controller 附加到请求上 signal: controller.signal, }); // 将成功响应写入 state } catch (e) { // 处理错误含 AbortError } })(); // 组件卸载时中止请求 return () controller?.abort(); }, []);Axios 示例useEffect(() { // 创建 controller let controller new AbortController(); (async () { try { const response await axios.get(APIEndpoint, { // 将 controller 附加到请求上 signal: controller.signal, }); // 将成功响应写入 state } catch (e) { // 处理错误 } })(); // 组件卸载时中止请求 return () controller?.abort(); }, []);2. 超时Timeouts在useEffect中使用setTimeout(callback, timeInMs)启动计时器并在清理函数中调用clearTimeout(timerId)。这保证组件卸载时计时器一定被清除useEffect(() { let timerId setTimeout(() { // 执行某个操作如 state 更新 timerId null; }, 5000); // 组件卸载时清除计时器 return () clearTimeout(timerId); }, []);3. 间隔Intervals同理setInterval(callback, timeInMs)在useEffect中声明clearInterval(intervalId)放进清理函数即可确保组件卸载时定时器停止useEffect(() { let intervalId setInterval(() { // 执行某个操作如 state 更新 intervalId null; }, 5000); // 组件卸载时清除 interval return () clearInterval(intervalId); }, []);这正是 useLoadingOvertime 内部采用的模式Refine 用它来统计加载超时overtime而清理函数保证了每次加载状态变化或组件卸载时计时器都被彻底回收。4. 事件监听器Event Listeners事件监听器在useEffect回调中通过addEventListener附加到元素上并在清理函数中用removeEventListener移除保证组件卸载时监听器被摘除。一个典型应用是点击元素外部自定义 hookconst useOutsideClick (callback: () void) { const ref useRefHTMLElement(); useEffect(() { const handleClick (event: Event) { if (ref.current !ref.current.contains(event.target as Node)) { callback(); } }; document.addEventListener(click, handleClick, true); return () { document.removeEventListener(click, handleClick, true); }; }, [callback, ref]); return ref; };注意addEventListener/removeEventListener必须使用同一个函数引用如示例中的handleClick且 capture 参数一致此处为true否则无法正确移除监听。5. WebSocket 与实时订阅在组件中创建WebSocket连接后可以把socket.close()放进清理函数在组件卸载时关闭连接useEffect(() { const ws new WebSocket(url, protocols); // 执行 socket 相关的操作 // 组件卸载时清理 websocket return () ws.close(); }, []);Refine 的实时realtime能力就是这一模式的框架级体现。核心 hook useSubscription 在 effect 中调用liveProvider.subscribe(...)建立订阅并在返回的清理函数中调用liveProvider.unsubscribe(subscription)撤销订阅useEffect(() { let subscription: any; if (enabled) { subscription liveProvider?.subscribe({ channel, params, types, callback: onLiveEvent, meta: { ...meta, dataProviderName: meta?.dataProviderName ?? default, }, }); } return () { if (subscription) { liveProvider?.unsubscribe(subscription); } }; }, [enabled]);同目录下的 useResourceSubscription 则进一步在订阅回调中按liveMode如auto触发查询缓存失效invalidate同样以unsubscribe作为清理逻辑。而具体的订阅/退订实现由各个 live provider 完成例如 ably provider 的subscribe保存{ channelInstance, listener }unsubscribe则调用channelInstance.unsubscribe(listener)——与addEventListener/removeEventListener的成对逻辑完全同构。另外useForm hook 中还展示了一种更业务向的清理用法在 effect 中不建立副作用而是纯粹利用卸载前的清理时机执行收尾动作——当autoSave.invalidateOnUnmount为真且表单已被自动保存过时在清理函数中调用invalidate(...)使列表/详情缓存失效React.useEffect(() { // autosaved 一旦被置为 true 就不会再变回 false // 因此 invalidate 只在 hook 生命周期结束时被调用一次 return () { if ( props.autoSave?.invalidateOnUnmount autosaved identifier typeof id ! undefined ) { invalidate({ id, invalidates: props.invalidates || [list, many, detail], dataProviderName: props.dataProviderName, resource: identifier, }); } }; }, [props.autoSave?.invalidateOnUnmount, autosaved]);从源码结构看这种只返回清理函数、effect 主体为空的写法等价于在componentWillUnmount时执行一次性收尾逻辑。实践要点小结场景建立effect 内拆除清理函数内API 请求fetch/axiossignalcontroller.abort()一次性计时器setTimeoutclearTimeout(timerId)循环计时器setIntervalclearInterval(intervalId)事件监听addEventListener(el, evt, fn, opts)removeEventListener(el, evt, fn, opts)WebSocket / 实时订阅new WebSocket(url)/subscribe(...)ws.close()/unsubscribe(...)卸载收尾空一次性执行缓存失效等逻辑关键结论成对思维每种建立操作都应有对应的拆除操作两者写在同一个 effect 中参数保持一致同一个 listener、同一个 signal、同一个 timerId触发时机不止卸载依赖项变化时清理函数会先于新 effect 执行因此它也是参数变了、重建资源的标准做法框架内部同样依赖它Refine 的useLoadingOvertime定时器清理与状态复位、useSubscription/useResourceSubscription订阅退订、useForm卸载时缓存失效都以此模式为基础验证了该机制在真实工程中的普适性。理解何时以及如何用清理函数修复内存泄漏是成为一名更成熟的 React 开发者的关键一步而在 Refine 这类管理后台框架上构建应用时正确掌握它还能帮助你理解实时列表、加载超时提示、表单自动保存等特性背后的行为契约。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考