window.innerWidth引发Hydration错误,寻求技术解决方案
解决SSR下窗口宽度Hook导致的Hydration不匹配问题
问题根源
服务器端渲染时不存在window对象,你的UseWindowSize Hook初始会返回undefined,而客户端hydrate时,虽然初始状态也是undefined,但useEffect会立刻执行并更新真实窗口宽度,导致客户端首次渲染的UI(基于更新后的宽度)和服务器渲染的UI(基于undefined的判断)不一致,触发类名不匹配和Hydration失败错误。
修复方案
1. 统一初始状态,延迟客户端UI切换
修改Hook,让服务器和客户端的初始状态保持一致,同时在客户端hydrate完成后再更新真实窗口大小。使用Hook的组件需先渲染和服务器一致的占位内容,直到获取到真实窗口宽度后再切换。
2. 修改后的UseWindowSize Hook
import { useState, useEffect, useRef, useCallback } from 'react'; export default function UseWindowSize() { const isClient = typeof window === 'object'; const lastWidth = useRef<number>(); // 服务器和客户端初始状态统一为null const [windowSize, setWindowSize] = useState<number | null>(null); const getSize = useCallback((): number => { return isClient ? window.innerWidth : 0; }, [isClient]); useEffect(() => { if (!isClient) return; // 客户端首次加载立即同步真实窗口大小 const initialWidth = getSize(); lastWidth.current = initialWidth; setWindowSize(initialWidth); function handleResize() { const width = getSize(); if (width !== lastWidth.current) { lastWidth.current = width; setWindowSize(width); } } window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [getSize, isClient]); return windowSize; };
3. 组件中使用Hook的正确方式
在组件里先判断windowSize是否为null,如果是则渲染和服务器一致的默认布局,直到获取到真实宽度后再调整:
import UseWindowSize from './UseWindowSize'; function Header() { const windowSize = UseWindowSize(); // 初始阶段服务器和客户端统一渲染mobile-header,获取到真实宽度后再切换 const className = windowSize === null ? 'mobile-header' : windowSize >= 768 ? 'header' : 'mobile-header'; return <header className={className}>...</header>; }
额外说明
服务器端无法获取客户端窗口宽度,必须接受服务器渲染默认布局,客户端hydrate完成后再根据真实窗口大小调整UI,以此避免初始渲染的内容不匹配。
内容的提问来源于stack exchange,提问作者shifoc
相关产品推荐
相关产品推荐

