news 2026/10/13 8:17:20

30-seconds-of-react 实战:useWindowSize 自定义 Hook 追踪浏览器窗口尺寸

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
30-seconds-of-react 实战:useWindowSize 自定义 Hook 追踪浏览器窗口尺寸
  • 前端
  • 教程

【免费下载链接】30-seconds-of-react

Short React code snippets for all your development needs

项目地址:https://gitcode.com/gh_mirrors/30/30-seconds-of-react
点击查看免费下载

在响应式布局、自适应弹层定位、移动端与桌面端行为切换等场景中,组件经常需要感知浏览器视口的实时宽度和高度。30-seconds-of-react 仓库中的 use-window-size.md 提供了一个简洁的自定义 HookuseWindowSize,它基于useState与useEffect监听窗口resize事件,把窗口尺寸变成可参与渲染的 React 状态。读完本文,你将掌握该 Hook 的完整实现、每一步设计意图(包括为何初始值要设为undefined),以及它与仓库内其他窗口相关 Hook 的选型差异。

一、useWindowSize 是什么

该片段在 snippets/use-window-size.md 中的一句话定义是:Tracks the dimensions of the browser window(追踪浏览器窗口的尺寸)。它的 frontmatter 元信息声明了标签hooks、state、effect,也即它的实现只依赖 React 原生的状态与副作用机制,不引入任何第三方库。

仓库中每个 snippet 都遵循 snippet-template.md 规定的结构:frontmatter 元数据 + 一句功能描述 + 要点式原理说明 + 两段独立代码(Hook 定义、组件用法)。useWindowSize也完全符合这一规范,因此本文的讲解也按“实现 → 解析 → 使用”的顺序展开。

二、完整实现代码

以下是文档中给出的useWindowSize完整实现(可直接复制到任何 React 18+ 项目中使用):

const useWindowSize = () => { const [windowSize, setWindowSize] = React.useState({ width: undefined, height: undefined, }); React.useEffect(() => { const handleResize = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }); window.addEventListener('resize', handleResize); handleResize(); return () => { window.removeEventListener('resize', handleResize); }; }, []); return windowSize; };

Hook 返回一个{ width, height }对象,调用方通过解构即可拿到当前窗口的宽(像素)与高(像素)。

三、实现步骤逐条解析

原文档用三条要点概括了实现思路,下面结合代码逐条展开。

1. 用useState初始化尺寸状态,且初始值刻意设为undefined

const [windowSize, setWindowSize] = React.useState({ width: undefined, height: undefined, });

这里有一个容易被忽略但很关键的设计:初始值不是0或某个硬编码数字,而是width: undefined, height: undefined。

文档明确指出这样做的目的是“避免服务端与客户端渲染结果不一致”(avoid mismatch between server and client renders)。原因在于:如果初始值写成0 x 0,SSR 阶段服务端输出的是0 x 0,而浏览器水合(hydration)后useEffect立刻把尺寸刷新为真实值(如1920 x 1080),首屏就会出现可见的“跳变”,且严格模式下水合期间服务端 HTML 与客户端首次渲染的 DOM 不一致还会触发 React 的 hydration 警告。将初始值设为undefined后,服务端与客户端在“尺寸未知”这一状态下输出的是同一份 HTML,真实尺寸统一交给挂载后的副作用去填充,从机制上消除了水合不匹配。

2. 定义基于Window.innerWidth/Window.innerHeight的更新函数

const handleResize = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight });

handleResize每次被调用时都会读取window.innerWidth与window.innerHeight(浏览器视口尺寸,单位 CSS 像素),并将状态更新为一个新的对象字面量。这里值得注意两点:

  • 每次都生成新对象引用,而不是复用旧对象。由于 React 对useState的更新使用Object.is浅比较来决定是否触发重渲染,如果错误地写成setWindowSize(windowSize)复用同一引用,组件将不会重新渲染;
  • 读取的是视口(viewport)尺寸,不包含浏览器工具栏、滚动条等,这与window.outerWidth(整个浏览器窗口外框尺寸)的语义不同,布局计算应以前者为准。

3. 用useEffect挂载时注册resize监听,卸载时清理

React.useEffect(() => { const handleResize = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }); window.addEventListener('resize', handleResize); handleResize(); return () => { window.removeEventListener('resize', handleResize); }; }, []);

这段副作用有三个要点:

  • 监听器只在挂载时注册一次:依赖数组为空数组[],handleResize在 effect 闭包内定义且只依赖setWindowSize(其引用在多次渲染间稳定),因此不会造成闭包陷阱,也不会随每次渲染重复绑定;
  • 立即调用一次handleResize():这是状态填充的“首帧”。由于 effect 只在组件挂载后运行,如果不手动调用,windowSize将一直保持undefined,直到用户第一次拖拽窗口才更新。挂载即调用,保证了首次渲染之后尺寸立刻变为真实值;
  • 返回清理函数移除监听:当使用 Hook 的组件卸载时,window.removeEventListener('resize', handleResize)会执行。window是全局单例,若不移除监听,已卸载组件的setWindowSize仍会在每次窗口缩放时被调用,既造成无意义的重复渲染,也构成监听器泄漏。

四、在组件中使用

文档给出的使用示例如下,展示了典型的“解构 + 渲染”消费方式:

const MyApp = () => { const { width, height } = useWindowSize(); return ( <p> Window size: ({width} x {height}) </p> ); }; ReactDOM.createRoot(document.getElementById('root')).render( <MyApp /> );

任何需要响应窗口尺寸的逻辑都可以基于这个状态派生,例如:

const { width } = useWindowSize(); const isMobile = width !== undefined && width < 768; // 根据 isMobile 切换布局

注意width !== undefined的守卫:首次 effect 执行前尺寸仍为undefined,比较运算前应显式判断,这与第二节说明的初始值设计是配套的。

五、与仓库内相关 Hook 的对比与选型

30-seconds-of-react 仓库中还有两个与窗口行为相关的 Hook,理解它们的差异有助于按场景选型:

  • use-on-window-resize.md(useOnWindowResize):回调式 API,只在你需要“尺寸变化时执行一段逻辑”但不需要把尺寸本身作为渲染状态时使用。它用useRef持有监听引用、以[callback]作为依赖数组在回调变化时重绑。若你的需求是“缩放时重绘 canvas”,选它比useWindowSize更轻量,因为不会驱动任何组件重渲染。
  • use-media-query.md(useMediaQuery):基于window.matchMedia判断视口是否命中某条媒体查询表达式(如'(max-width: 400px)')。如果你的目标只是“小于某个断点时切换 UI”,直接用媒体查询更贴切,还能复用 CSS 断点定义;而useWindowSize的优势在于拿到的是精确像素值,适合需要数值参与计算的布局逻辑。

另外,若你的项目涉及 SSR,可以配合仓库的 use-ssr.md(useSSRHook)来显式判断当前运行在浏览器还是服务端,其canUseViewport属性正是针对“是否可用视口 API”这一前提设计的。

六、适用前提与扩展建议

基于该仓库片段的实际内容,使用useWindowSize时需注意以下前提:

  • 适用环境:代码直接引用了全局window对象,因此useEffect中的逻辑只在浏览器端执行(effect 本身就不会在 SSR 阶段运行,这与把 DOM 读取放在 effect 内而不是渲染期是 React 处理浏览器 API 的惯用模式)。在纯客户端渲染(CSR)项目中可以直接使用,无需额外判空。
  • 渲染频率:每次resize事件都会触发一次状态更新与重渲染。现代浏览器的resize事件频率已受系统节流,通常无需处理;如果下游逻辑(如昂贵的图表重绘)对渲染敏感,可将width传给仓库的 use-debounce.md 提供的防抖 Hook 后再消费。
  • iframe 环境:window.innerWidth读取的是当前 frame 的视口。若页面被嵌入 iframe,得到的是 iframe 的尺寸而非浏览器窗口尺寸,这是 Web 平台的固有行为,使用数值前需明确当前 frame 上下文。

七、小结

useWindowSize是整个 30-seconds-of-react 仓库中体现“最小可用自定义 Hook”模式的典型样本:useState保存派生自浏览器 API 的状态、useEffect负责注册与清理监听、undefined初始值兼容服务端渲染、effect 末尾的清理函数保证卸载安全。完整实现见 snippets/use-window-size.md,仓库贡献规范见 CONTRIBUTING.md。掌握这套“状态 + 副作用”的组合方式后,你可以以同样的骨架快速扩展出追踪其他浏览器实时信息(如屏幕方向、设备像素比)的 Hook。

  • 前端
  • 教程

【免费下载链接】30-seconds-of-react

Short React code snippets for all your development needs

项目地址:https://gitcode.com/gh_mirrors/30/30-seconds-of-react
点击查看免费下载
上一篇:ncmdumpGUI终极教程:3分钟掌握网易云音乐NCM文件转换技巧
下一篇:城通网盘直连解析工具:告别30秒等待,3秒获取高速下载链接的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 8:16:49

MATLAB与Maxwell联合实现电机多物理场自动优化

1. 项目背景与核心价值多物理场联合优化在电机设计领域一直是个技术难点。传统串行优化流程需要工程师反复在电磁场仿真软件和优化算法平台之间切换&#xff0c;不仅效率低下&#xff0c;还容易丢失关键数据。这个案例展示了如何通过MATLAB与Maxwell的深度集成&#xff0c;实现…

作者头像 李华
网站建设 2026/10/13 8:16:26

多Skill场景下的分层加载机制:从全量堆砌到按需调用的Agent技能管理实践

1. 从“技能堆积”到“按需调用”的认知转变 手里攒了上百个 Skill&#xff0c;这大概是每个深度折腾 Agent 的人都会经历的阶段。一开始特别兴奋&#xff0c;每看到一个有意思的能力就忍不住封装成 Skill 塞进去——查天气的、读文件的、调接口的、做格式转换的、跑数据清洗的…

作者头像 李华
网站建设 2026/10/13 8:15:49

PAT乙级乒乓球筐模拟题:计数数组与边界处理实战

1. 从"乒乓球筐"这个标题说起&#xff1a;PAT乙级里被低估的模拟题第一次看到"乒乓球筐"这个题目名&#xff0c;很多人会以为这是一道关于体育器材的简单题&#xff0c;甚至有人直接跳过。但如果你刷过PAT乙级题库&#xff0c;就会知道这类"名字朴素、…

作者头像 李华
网站建设 2026/10/13 8:14:22

光收发模块设计难点解析:从光电芯片到信号完整性与可靠性验证

1. 光收发模块为什么难做&#xff1a;从“发光”到“收光”的系统工程光收发模块&#xff0c;行业内一般直接叫光模块&#xff0c;是光通信系统里负责光电转换的核心器件。它做的事情概括起来只有两件&#xff1a;发射端把高速电信号调制成光信号送进光纤&#xff0c;接收端把光…

作者头像 李华
网站建设 2026/10/13 8:13:50

基于SpringBoot的健身预约系统开题答辩:从表设计到高频追问应对

开题答辩这种事&#xff0c;说起来是十分钟汇报加十分钟提问&#xff0c;但真正决定成败的从来不是临场口才&#xff0c;而是答辩之前你有没有把“这个题到底怎么落地”想清楚。以某高校本科生常见的题目——基于SpringBoot的健身预约系统的设计与实现为例&#xff0c;这篇文章…

作者头像 李华