You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React移动端侧边栏刷新状态重置问题及解决方案咨询

解决移动端侧边栏刷新恢复显示、桌面端默认展开的问题

我实现了可隐藏/显示的侧边栏菜单,但现在遇到两个问题:

  • 移动端隐藏侧边栏后刷新页面,侧边栏会恢复初始显示状态
  • 希望桌面端默认保持侧边栏展开

我考虑用useEffect修改初始状态,但不确定是否可行。以下是我的ContextProvider.js和Sidebar组件代码:

ContextProvider.js

import { useState, createContext, useContext, useEffect } from "react";

const StateContext = createContext()
const initialState = {
    cart: false,
    notification: false,
    searchBar: false,
}

export const ContextProvider = ({ children }) => {
    const [activeMenu, setActiveMenu] = useState(true)
    const [isClicked, setIsClicked] = useState(initialState)
    const [screenSize, setScreenSize] = useState(undefined)
    const [currentColor, setCurrentColor] = useState('#bf9a8e')
    const [currentMode, setCurrentMode] = useState('Light')
    const [isOpen, setIsOpen] = useState(false)
    const [currentIndex, setCurrentIndex] = useState(0)

    const setMode = (e) => {
        setCurrentMode(e.target.value);
        localStorage.setItem('themeMode', e.target.value);
      };

      const setColor = (color) => {
        setCurrentColor(color);
        localStorage.setItem('colorMode', color);
      };

    const handleClick = (clicked) => {
        setIsClicked({...initialState, [clicked]: true})
    }

    const slideLeft = () => {
        let slider = document.getElementById('slider-2')
        slider.scrollLeft = slider.scrollLeft - 500
      }

    const slideRight = () => {
        let slider = document.getElementById('slider-2')
        slider.scrollLeft = slider.scrollLeft + 500
      }


    return (
        <StateContext.Provider value={{ 
            activeMenu,
            setActiveMenu,
            isClicked,
            setIsClicked,
            handleClick,
            screenSize,
            setScreenSize, currentColor,
            currentMode,
            setColor,
            setMode,
            isOpen, 
            setIsOpen, 
            slideLeft,
            slideRight,
            currentIndex, 
            setCurrentIndex, 
         }}>
            {children}
        </StateContext.Provider>
    )
}

export const useStateContext = () => useContext(StateContext)

Sidebar组件

function Sidebar() {

  const { activeMenu, setActiveMenu, screenSize, currentColor } = useStateContext();

  const handleCloseSideBar = () => {
    if (activeMenu !== undefined && screenSize <= 900) {
      setActiveMenu(false);
    }
  };

  const activeLink = 'flex items-center gap-5 pl-4 pt-3 pb-2.5 text-gray-100 dark:text-white rounded-lg bg-slate-700 text-md m-2'
  const normalLink = 'flex items-center gap-5 pl-4 pt-3 pb-2.5 rounded-lg text-md dark:text-gray-300 text-gray-500 dark:hover:text-gray-600 m-2 hover:bg-gray-200';

  return (
    <div className="mr-4 ml-4 mt-3 scrollbar-hide h-screen md:overflow-hidden overflow-auto md:hover:overflow-auto pb-10">
      {/* LOGO SECTION */}
      {activeMenu && (
        <>
          <div className="flex text-slate-700 dark:text-white justify-between">
            <Link
              to="/"
              onClick={handleCloseSideBar}
              className="flex text-xl font-extrabold items-center gap-3 ml-4 mt-4 tracking-tight"
            >
              <GiPlanetCore/>
              <span>BaIlEyWoRld</span>
            </Link>
            <button
              type="button"
              onClick={() => setActiveMenu((prevActiveMenu) => !prevActiveMenu)}
              className="text-2xl p-3 mt-4 block sm:hidden"
            >
              <MdClose />
            </button>
          </div>
          <div className="mt-10">
            {links.map((item) => (
              <div key={item.title}>
                <p className="text-gray-400 dark:text-gray-200 font-semibold uppercase m-4 mt-4">
                  {item.title}
                </p>
                {item.links.map((link) => (
                  <NavLink
                    to={`/${link.name}`}
                    key={link.name}
                    onClick={handleCloseSideBar}
                    className={({isActive}) =>
                      isActive ? activeLink : normalLink
                    }
                    style={({isActive}) => ({backgroundColor: isActive ? currentColor : ''})}
                  >
                    {link.icon}
                    <span>{link.name}</span>
                  </NavLink>
                ))}
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

export default Sidebar

解决方案

核心思路是用localStorage持久化移动端侧边栏状态,同时结合屏幕尺寸判断,强制桌面端始终展开侧边栏。具体修改如下:

修改后的ContextProvider.js

import { useState, createContext, useContext, useEffect } from "react";

const StateContext = createContext()
const initialState = {
    cart: false,
    notification: false,
    searchBar: false,
}

export const ContextProvider = ({ children }) => {
    // 初始化状态:桌面端直接展开,移动端读取本地存储,默认展开
    const getInitialActiveMenu = () => {
        const saved = localStorage.getItem('activeMenu');
        const isDesktop = typeof window !== 'undefined' && window.innerWidth > 900;
        return isDesktop ? true : (saved ? JSON.parse(saved) : true);
    }

    const [activeMenu, setActiveMenu] = useState(getInitialActiveMenu)
    const [isClicked, setIsClicked] = useState(initialState)
    // 初始化屏幕尺寸,兼容服务端渲染场景
    const [screenSize, setScreenSize] = useState(typeof window !== 'undefined' ? window.innerWidth : undefined)
    const [currentColor, setCurrentColor] = useState('#bf9a8e')
    const [currentMode, setCurrentMode] = useState('Light')
    const [isOpen, setIsOpen] = useState(false)
    const [currentIndex, setCurrentIndex] = useState(0)

    // 监听屏幕尺寸变化,桌面端强制展开侧边栏
    useEffect(() => {
        const handleResize = () => {
            const width = window.innerWidth;
            setScreenSize(width);
            if (width > 900) {
                setActiveMenu(true);
                localStorage.setItem('activeMenu', JSON.stringify(true));
            }
        }

        window.addEventListener('resize', handleResize);
        return () => window.removeEventListener('resize', handleResize);
    }, [])

    // 自定义状态更新函数,仅移动端保存状态到本地存储
    const customSetActiveMenu = (value) => {
        setActiveMenu(value);
        if (screenSize <= 900) {
            localStorage.setItem('activeMenu', JSON.stringify(value));
        }
    }

    const setMode = (e) => {
        setCurrentMode(e.target.value);
        localStorage.setItem('themeMode', e.target.value);
    };

    const setColor = (color) => {
        setCurrentColor(color);
        localStorage.setItem('colorMode', color);
    };

    const handleClick = (clicked) => {
        setIsClicked({...initialState, [clicked]: true})
    }

    const slideLeft = () => {
        let slider = document.getElementById('slider-2')
        slider.scrollLeft = slider.scrollLeft - 500
      }

    const slideRight = () => {
        let slider = document.getElementById('slider-2')
        slider.scrollLeft = slider.scrollLeft + 500
      }


    return (
        <StateContext.Provider value={{ 
            activeMenu,
            setActiveMenu: customSetActiveMenu, // 替换为自定义更新函数
            isClicked,
            setIsClicked,
            handleClick,
            screenSize,
            setScreenSize, currentColor,
            currentMode,
            setColor,
            setMode,
            isOpen, 
            setIsOpen, 
            slideLeft,
            slideRight,
            currentIndex, 
            setCurrentIndex, 
         }}>
            {children}
        </StateContext.Provider>
    )
}

export const useStateContext = () => useContext(StateContext)

优化后的Sidebar组件(可选)

简化侧边栏关闭逻辑:

function Sidebar() {

  const { activeMenu, setActiveMenu, screenSize, currentColor } = useStateContext();

  const handleCloseSideBar = () => {
    // 移动端点击链接后自动关闭侧边栏
    if (screenSize <= 900) {
      setActiveMenu(false);
    }
  };

  const activeLink = 'flex items-center gap-5 pl-4 pt-3 pb-2.5 text-gray-100 dark:text-white rounded-lg bg-slate-700 text-md m-2'
  const normalLink = 'flex items-center gap-5 pl-4 pt-3 pb-2.5 rounded-lg text-md dark:text-gray-300 text-gray-500 dark:hover:text-gray-600 m-2 hover:bg-gray-200';

  return (
    <div className="mr-4 ml-4 mt-3 scrollbar-hide h-screen md:overflow-hidden overflow-auto md:hover:overflow-auto pb-10">
      {/* LOGO SECTION */}
      {activeMenu && (
        <>
          <div className="flex text-slate-700 dark:text-white justify-between">
            <Link
              to="/"
              onClick={handleCloseSideBar}
              className="flex text-xl font-extrabold items-center gap-3 ml-4 mt-4 tracking-tight"
            >
              <GiPlanetCore/>
              <span>BaIlEyWoRld</span>
            </Link>
            <button
              type="button"
              onClick={() => setActiveMenu((prevActiveMenu) => !prevActiveMenu)}
              className="text-2xl p-3 mt-4 block sm:hidden"
            >
              <MdClose />
            </button>
          </div>
          <div className="mt-10">
            {links.map((item) => (
              <div key={item.title}>
                <p className="text-gray-400 dark:text-gray-200 font-semibold uppercase m-4 mt-4">
                  {item.title}
                </p>
                {item.links.map((link) => (
                  <NavLink
                    to={`/${link.name}`}
                    key={link.name}
                    onClick={handleCloseSideBar}
                    className={({isActive}) =>
                      isActive ? activeLink : normalLink
                    }
                    style={({isActive}) => ({backgroundColor: isActive ? currentColor : ''})}
                  >
                    {link.icon}
                    <span>{link.name}</span>
                  </NavLink>
                ))}
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

export default Sidebar

内容的提问来源于stack exchange,提问作者andrea siano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 14:40:56