- 前端
- 教程
【免费下载链接】30-seconds-of-react
Short React code snippets for all your development needs
在响应式布局、自适应弹层定位、移动端与桌面端行为切换等场景中,组件经常需要感知浏览器视口的实时宽度和高度。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
相关推荐
react-use 的 useWindowSize:实时追踪浏览器窗口尺寸的 Sensor Hook 完全指南
react use 的 useWindowSize:实时追踪浏览器窗口尺寸的 Sensor Hook 完全指南 useWindowSize 是 react us
前端30 seconds of React 之 useSearchParam Hook:用自定义 Hook 实时追踪浏览器 URL 查询参数
30 seconds of React 之 useSearchParam Hook:用自定义 Hook 实时追踪浏览器 URL 查询参数 在 React 开发中
前端教程30-seconds-of-react 源码剖析:useOnWindowResize 窗口尺寸监听 Hook 的实现与实战
30 seconds of react 源码剖析:useOnWindowResize 窗口尺寸监听 Hook 的实现与实战 本篇技术指南以开源仓库 30 sec
前端教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考