React部署版本导航栏响应式异常问题求助
React响应式导航栏部署后异常排查
问题描述
本地运行时导航栏响应式表现正常:移动端显示汉堡菜单、PC端显示完整导航栏;但部署后出现反转——PC端显示移动端汉堡菜单,移动端反而显示PC端的完整导航栏。
相关代码
Header组件
function Header({scrolling}) { const [isBurger, setIsBurger] = useState(false); const [isNav, setIsNav] = useState(false); const [isSearchBar, setIsSearchBar] = useState(false); const [isScreenSize, setIsScreenSize] = useState(0); const { width } = useWindowSize(); // 汉堡菜单点击事件 const handleClickOnBurger = () => { setIsBurger((prevIsBurger) => !prevIsBurger); setIsNav((prevIsNav) => !prevIsNav); } // 搜索栏点击事件 const handleClickOnSearch = () => { setIsSearchBar((prevIsSearchbar) => !prevIsSearchbar) } function checkWidth() { if (isScreenSize >= 768) { setIsSearchBar(false); setIsBurger(false); setIsNav(true); } } useEffect(() => { setIsScreenSize(width); checkWidth(); }); return ( <div className="d-flex flex-row flex-wrap justify-content-between align-items-center py-md-4 px-md-5 my-3 shadow-sm headerBody"> <Navbar callFunc={handleClickOnBurger} btnHook={isBurger} navHook={isNav} scroller={scrolling} /> {!isSearchBar && <Logo />} <Searchbar callFunc={handleClickOnSearch} btnHook={isSearchBar} /> </div> ); } function useWindowSize() { // 初始状态设为undefined,保证服务端与客户端渲染匹配 const [windowSize, setWindowSize] = useState({ width: undefined, height: undefined, }); useEffect(() => { // 窗口尺寸变化处理函数 function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); } window.addEventListener("resize", handleResize); // 立即执行一次,获取初始窗口尺寸 handleResize(); // 清除监听 return () => window.removeEventListener("resize", handleResize); }, []); // 空数组确保仅在挂载时执行 return windowSize; } export default Header;
Navbar组件
import NavigationLinks from "./NavigationLinks"; import { Icon } from "@iconify/react"; function Navbar(props) { let icon; const burgerIcon = "iconamoon:menu-burger-horizontal-bold"; const xIcon = "tabler:x"; const navBarLinks = [ { text: "home", path: "/#heroHome" }, { text: "about", path: "/#about" }, { text: "for partners", path: "/#partners" } ] const styleMid = "visibilityHiddenMid"; const styleSmall = "visibilityHiddenSmall navbarSmall"; let styleFinal; return ( <> <span className="visually-hidden"> {styleFinal = props.btnHook ? styleSmall : styleMid} {icon = props.btnHook ? xIcon : burgerIcon} </span> <button className="bg bg-transparent ms-2 border border-0 visibilityHiddenSmall" onClick={props.callFunc}> <Icon icon={icon} className="color text-black fs-1 fw-lighter bg-transparent" /> </button> <div className={"col-12 col-md-4 " + styleFinal}> <div className="d-flex flex-column flex-md-row justify-content-start justify-content-md-left mx-3 my-4 my-md-0 align-items-strat align-items-md-center"> {navBarLinks.map((link, index) => { return (<NavigationLinks text={link.text} path={link.path} key={index} />) })} </div> </div> </> ); } export default Navbar;
问题根源与修复方案
核心问题
- useEffect依赖缺失:Header组件中的
useEffect没有指定依赖数组,会触发无限重渲染,部署环境(尤其是SSR/SSG场景)下会导致状态混乱。 - 状态同步延迟:额外维护
isScreenSize状态,导致窗口尺寸变化时状态更新存在延迟,与实际尺寸不匹配。 - SSR适配不足:
useWindowSize初始状态为undefined,部署时服务端渲染无window对象,初始尺寸无法正确获取,与本地客户端渲染的初始状态不一致。
修复步骤
1. 优化useWindowSize钩子,适配SSR
function useWindowSize() { // 客户端环境直接获取初始尺寸,服务端环境设为undefined const [windowSize, setWindowSize] = useState({ width: typeof window !== 'undefined' ? window.innerWidth : undefined, height: typeof window !== 'undefined' ? window.innerHeight : undefined, }); useEffect(() => { function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); } window.addEventListener("resize", handleResize); handleResize(); return () => window.removeEventListener("resize", handleResize); }, []); return windowSize; }
2. 修正Header组件的状态逻辑与useEffect依赖
function Header({scrolling}) { const [isBurger, setIsBurger] = useState(false); const [isNav, setIsNav] = useState(false); const [isSearchBar, setIsSearchBar] = useState(false); const { width } = useWindowSize(); const handleClickOnBurger = () => { setIsBurger(prev => !prev); setIsNav(prev => !prev); } const handleClickOnSearch = () => { setIsSearchBar(prev => !prev); } useEffect(() => { // 处理服务端渲染时width为undefined的情况 if (width === undefined) return; if (width >= 768) { // PC端:显示完整导航,隐藏汉堡按钮与搜索栏 setIsSearchBar(false); setIsBurger(false); setIsNav(true); } else { // 移动端:隐藏导航,显示汉堡按钮 setIsNav(false); setIsBurger(false); } }, [width]); // 仅在width变化时执行 return ( <div className="d-flex flex-row flex-wrap justify-content-between align-items-center py-md-4 px-md-5 my-3 shadow-sm headerBody"> <Navbar callFunc={handleClickOnBurger} btnHook={isBurger} navHook={isNav} scroller={scrolling} /> {!isSearchBar && <Logo />} <Searchbar callFunc={handleClickOnSearch} btnHook={isSearchBar} /> </div> ); }
3. 简化Navbar组件的样式逻辑
import NavigationLinks from "./NavigationLinks"; import { Icon } from "@iconify/react"; function Navbar(props) { const burgerIcon = "iconamoon:menu-burger-horizontal-bold"; const xIcon = "tabler:x"; const navBarLinks = [ { text: "home", path: "/#heroHome" }, { text: "about", path: "/#about" }, { text: "for partners", path: "/#partners" } ]; // 直接在渲染时判断样式与图标,避免JSX中的副作用赋值 const styleFinal = props.btnHook ? "visibilityHiddenSmall navbarSmall" : "visibilityHiddenMid"; const icon = props.btnHook ? xIcon : burgerIcon; return ( <> <button className="bg bg-transparent ms-2 border border-0 visibilityHiddenSmall" onClick={props.callFunc}> <Icon icon={icon} className="color text-black fs-1 fw-lighter bg-transparent" /> </button> <div className={`col-12 col-md-4 ${styleFinal}`}> <div className="d-flex flex-column flex-md-row justify-content-start justify-content-md-left mx-3 my-4 my-md-0 align-items-start align-items-md-center"> {navBarLinks.map((link, index) => ( <NavigationLinks text={link.text} path={link.path} key={index} /> ))} </div> </div> </> ); } export default Navbar;
内容的提问来源于stack exchange,提问作者mbilm
相关产品推荐
相关产品推荐

