30-seconds-of-react 源码剖析:useOnWindowResize 窗口尺寸监听 Hook 的实现与实战
前端教程【免费下载链接】30-seconds-of-reactShort React code snippets for all your development needs项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-react点击查看免费下载本篇技术指南以开源仓库 30-seconds-of-react 中收录的 useOnWindowResize 自定义 Hook 为核心讲解如何用useRef()、useEffect()与EventTarget.addEventListener()组合在 React 函数组件中优雅地监听浏览器窗口resize事件。读完本文你将掌握该 Hook 的逐行实现原理、可复制的实战用法、与仓库内useOnWindowScroll、useWindowSize、useEventListener等同类 Hook 的异同以及依赖数组、监听器清理、SSR 兼容等关键边界处理方案。一、useOnWindowResize 是什么useOnWindowResize是 30-seconds-of-react 仓库snippets/目录下的一个自定义 Hook其作用一句话即可概括在窗口尺寸发生变化即触发resize事件时执行传入的回调函数。该片段在仓库中的元数据frontmatter标注为title: React useOnWindowResize hook type: snippet tags: [hooks,effect,event]从标签可以读出它的定位属于hooks自定义 Hook、依赖effect副作用处理、核心对象是event事件监听。它适用于所有需要感知视口尺寸变化的场景例如响应式布局中根据窗口宽度切换组件渲染形态图表、画布、拖拽区域的尺寸自适应重绘记录用户操作窗口的行为日志与防抖/节流结合处理高频触发的重计算任务。二、完整源码与逐行拆解原片段给出的完整实现如下见 use-on-window-resize.mdconst useOnWindowResize callback { const listener React.useRef(null); React.useEffect(() { if (listener.current) window.removeEventListener(resize, listener.current); listener.current window.addEventListener(resize, callback); return () { window.removeEventListener(resize, listener.current); }; }, [callback]); };整个 Hook 只有三个关键步骤下面逐行剖析。1. 用useRef()持有监听器引用const listener React.useRef(null);useRef(null)创建了一个跨渲染周期稳定存在的可变容器listener其初始值为null。之所以不用普通局部变量是因为函数组件每次渲染都会重新执行函数体普通变量会被重置而 ref 对象在组件的整个生命周期内保持同一引用且修改listener.current不会触发重新渲染——这正是保存事件监听器这类与渲染无关的副作用状态的恰当选择。2. 用useEffect()挂载resize监听器React.useEffect(() { if (listener.current) window.removeEventListener(resize, listener.current); listener.current window.addEventListener(resize, callback); ... }, [callback]);useEffect()的第一个参数effect 函数在组件挂载后、以及依赖数组[callback]中的值发生变化时执行首先检查listener.current是否已有值若有则先调用EventTarget.removeEventListener()移除旧监听器避免重复监听随后通过EventTarget.addEventListener(resize, callback)在Window全局对象上注册新的监听器并把返回值存入listener.current依赖数组[callback]意味着只要传入的callback引用发生变化effect 就会先清理旧监听、再挂载新监听保证窗口上始终只有最新回调在生效。3. 在清理阶段移除监听器return () { window.removeEventListener(resize, listener.current); };useEffect()支持返回一个清理函数React 会在两种时机调用它组件卸载时——相当于类组件生命周期方法componentWillUnmount()的行为仓库中另有 useComponentWillUnmount 专门封装了这一语义下一次 effect 执行之前——即依赖变化导致 effect 重跑时先清理再重建。清理函数中同样调用EventTarget.removeEventListener()移除resize监听保证组件卸载后不会留下悬挂在window上的监听器从而避免内存泄漏与跨组件的事件污染。三、实战用法示例原片段附带的示例组件演示了最直接的使用方式见 use-on-window-resize.mdconst App () { useOnWindowResize(() console.log(window size: (${window.innerWidth}, ${window.innerHeight})) ); return pResize the window and check the console/p; }; ReactDOM.createRoot(document.getElementById(root)).render( App / );运行后页面上会显示一段提示文案当你拖动改变浏览器窗口大小时控制台会不断输出形如window size: (1280, 720)的当前窗口尺寸。其中window.innerWidth与window.innerHeight分别代表视口viewport的宽度与高度含滚动条这两个只读属性是读取窗口尺寸最常用的 API。示例尾部使用ReactDOM.createRoot(...).render(...)挂载应用这是 React 18 引入的新式入口写法与仓库 snippet-template.md 中规定的示例格式保持一致可直接复制运行。四、实现原理三个 React API 的组合逻辑useOnWindowResize之所以值得学习在于它把 React 的三个基础 API 组合成了一个可复用的抽象其内在逻辑可以概括为一张生命周期对照表生命周期阶段行为对应代码组件挂载注册resize监听器window.addEventListener(resize, callback)callback变化移除旧监听器注册新监听器removeEventListeneraddEventListener组件卸载移除监听器避免泄漏effect 返回的清理函数依赖数组[callback]的设计意图依赖数组是理解这个 Hook 的关键。React 的useEffect()会通过Object.is比较依赖项的前后引用若父组件在每次渲染时都内联创建新的箭头函数传入callback的引用每次都不同effect 就会频繁重跑导致监听器被反复移除/重建若callback经过useCallback()稳定化引用不变effect 只在挂载时执行一次此后resize事件始终命中同一个回调。因此调用方应当尽量用useCallback()包裹回调以控制 effect 的执行频率详见第六节。为什么用 ref 而不是 stateuseState()的更新会触发重新渲染而事件监听器的注册/移除属于与渲染输出无关的副作用用 state 保存监听器引用只会带来无谓的重渲染开销。ref 恰好提供了跨渲染共享、变化不触发渲染的语义是保存监听器引用的正确工具。五、源码级注意点addEventListener的返回值问题在深入阅读这段源码时有一个容易被忽略的细节值得指出根据 Web 平台规范EventTarget.addEventListener()的返回值是undefined而不是监听器本身。也就是说listener.current实际被赋值为undefined。从源码结构看这带来两个直接影响首次挂载时if (listener.current)判断为假跳过清理行为正常但清理函数中的removeEventListener(resize, listener.current)传入的是undefined无法匹配到先前注册的callback监听器——当callback引用变化或组件卸载时旧的监听器实际上不会被移除存在监听器累积的隐患。这一写法在仓库原文中如此呈现属于可以理解的历史实现如果你希望在自己的项目中避免该隐患可以参照仓库内 useEventListener 的用 ref 保存回调本身的思路改写例如const useOnWindowResize callback { const savedCallback React.useRef(); React.useEffect(() { savedCallback.current callback; }, [callback]); React.useEffect(() { const handleResize () savedCallback.current(); window.addEventListener(resize, handleResize); return () window.removeEventListener(resize, handleResize); }, []); };改进版把监听器本体handleResize与回调的时效性分离监听器只注册一次回调通过 ref 实时更新卸载时用同一引用精确移除既消除了重复监听问题也避免了依赖数组频繁变化带来的重挂载开销。六、仓库内同类 Hook 横向对比30-seconds-of-react 的snippets/目录下收录了多个与窗口/事件监听相关的 Hook把它们放在一起对比能更清晰地理解useOnWindowResize的定位与取舍。useOnWindowScroll一模一样的滚动版useOnWindowScroll 与useOnWindowResize结构完全一致只是把事件名从resize换成了scroll回调示例中读取的是window.pageYOffset页面垂直滚动距离。两者共同构成了监听全局窗口事件并执行回调这一模式的模板。useWindowSize由事件驱动状态useWindowSize 走向了另一个方向它不是执行回调而是把窗口尺寸沉淀为组件状态。其实现用useState()初始化{ width: undefined, height: undefined }在useEffect()中注册resize监听并调用window.innerWidth/window.innerHeight更新 state最后把尺寸对象返回给组件用于渲染。两者选型建议只需在尺寸变化时做一件事如打日志、触发请求用useOnWindowResize需要把尺寸作为渲染依据如条件渲染、样式计算用useWindowSize。useEventListener通用事件监听器useEventListener 把事件名、回调、目标元素都参数化(type, handler, el window)并采用savedHandlerref 保存最新回调、单独的useEffect()负责按[type, el]挂载/卸载监听器的结构——这也是第五节改进版实现所借鉴的范式。它比useOnWindowResize更通用但代码量也更大。useOnGlobalEvent支持事件类型与选项的扩展版useOnGlobalEvent 是useOnWindowResize的超集额外接收type与options参数并用第二个 ref 记录上一次的{ type, options }以便在依赖变化时按旧参数精确移除监听器。当你需要监听mousemove、keydown等窗口级事件时它比硬编码resize的版本更灵活。Hook监听目标核心差异适用场景useOnWindowResizewindow的resize仅执行回调尺寸变化时触发副作用useOnWindowScrollwindow的scroll同上事件为滚动滚动位置变化时触发副作用useWindowSizewindow的resize返回尺寸 state需要尺寸参与渲染useEventListener任意元素任意事件完全参数化通用事件监听useOnGlobalEventwindow的任意事件支持type/options监听多类型全局事件七、实战建议与边界情况1. 用useCallback稳定回调避免监听器反复重建由于 effect 依赖[callback]回调引用不稳定会导致监听器频繁移除/重建。实践中应配合useCallback使用const handleResize React.useCallback(() { console.log(window size: (${window.innerWidth}, ${window.innerHeight})); }, []); useOnWindowResize(handleResize);2. 服务端渲染SSR安全window.addEventListener只会在useEffect()中执行而 effect 仅在客户端浏览器端运行服务端渲染阶段不会访问window因此该 Hook 天然兼容 SSR不会出现window is not defined的报错。需要留意的是回调中读取的window.innerWidth等值不应被用来驱动首屏渲染否则可能出现服务端与客户端不一致的 hydration 问题——这正是 useWindowSize 将初始值设为undefined的原因。3. 高频回调记得防抖resize事件在拖拽窗口边缘时会以极高的频率触发回调内若涉及重布局、重请求等昂贵操作建议结合仓库内的 useDebounce 对回调结果或副作用做防抖例如只关心停止拖拽后的最终尺寸。4. 回调内不要依赖闭包中的陈旧 state若回调中读取了组件 state而回调没有被useCallback正确依赖包裹可能读到过期值。稳妥做法是让回调只依赖 ref 或通过useCallback声明依赖必要时结合useRef同步最新值。八、小结useOnWindowResize是 30-seconds-of-react 中窗口事件监听系列最精简的代表作它以约 12 行代码完整示范了ref 保存副作用引用 effect 注册监听 清理函数卸载监听这一 React 副作用管理的标准范式。掌握它之后你可以轻松迁移到滚动监听useOnWindowScroll、尺寸状态化useWindowSize、通用事件监听useEventListener乃至多事件全局监听useOnGlobalEvent形成一套完整的窗口事件处理工具箱。若需要进一步深入可在仓库内阅读 snippet-template.md 了解片段的格式规范或直接查看 use-on-window-resize.md 原文。赞分享前端教程【免费下载链接】30-seconds-of-reactShort React code snippets for all your development needs项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-react点击查看免费下载相关推荐30-seconds-of-react 源码解析useOnGlobalEvent 全局事件监听 Hook30 seconds of react 源码解析useOnGlobalEvent 全局事件监听 Hook 导读 useOnGlobalEvent 是 30 s前端教程dcg backup包指南Borg、Restic、Rclone备份工具的防误删完整配置教程dcg backup包指南Borg、Restic、Rclone备份工具的防误删完整配置教程 dcg Destructive Command Guard 是一款AI 安全治理应用安全CLI开发工具30-seconds-of-react useMutationObserver用 30 行 Hook 封装 DOM 变化监听30 seconds of react useMutationObserver用 30 行 Hook 封装 DOM 变化监听 导读 本文围绕 30 secon前端教程上一篇CANN/ge LLM-DataDist初始化接口下一篇告别重复编码DBeaver SQL编辑器代码模板全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考