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

React路由切换时如何保持Material-UI侧边菜单展开状态?

解决React Material-UI侧边菜单路由跳转后收起的问题

核心原因

你遇到的问题大概率是菜单展开状态存在组件内部的local state中,路由跳转时(history.push()触发),侧边菜单组件可能会重新渲染或重新挂载,导致局部state被重置为初始的关闭状态,所以菜单会自动收起。


解决方法

方法1:将展开状态迁移到全局状态或持久化存储

把菜单的展开状态从组件内部state提升到全局状态管理(比如Context API、Redux),或者存入localStorage做持久化,这样路由切换时状态不会丢失。

示例(用localStorage持久化):

import { useState, useEffect } from 'react';
import { List, ListItem, Collapse, ExpandLess, ExpandMore } from '@material-ui/core';
import { useHistory } from 'react-router-dom';

const SideMenu = () => {
  const history = useHistory();
  // 初始化时从localStorage读取上次的展开状态
  const [expandedKey, setExpandedKey] = useState(() => {
    return localStorage.getItem('expandedMenuKey') || '';
  });

  // 状态更新时同步存入localStorage
  useEffect(() => {
    localStorage.setItem('expandedMenuKey', expandedKey);
  }, [expandedKey]);

  const toggleMenu = (key) => {
    setExpandedKey(prev => prev === key ? '' : key);
  };

  return (
    <List>
      {/* 主菜单项 */}
      <ListItem button onClick={() => toggleMenu('dashboard')}>
        Dashboard
        {expandedKey === 'dashboard' ? <ExpandLess /> : <ExpandMore />}
      </ListItem>
      {/* 子菜单 */}
      <Collapse in={expandedKey === 'dashboard'} unmountOnExit>
        <List component="div" disablePadding>
          <ListItem button onClick={() => history.push('/dashboard/overview')}>
            Overview
          </ListItem>
          <ListItem button onClick={() => history.push('/dashboard/analytics')}>
            Analytics
          </ListItem>
        </List>
      </Collapse>

      {/* 其他主菜单 */}
    </List>
  );
};

方法2:基于当前路由自动匹配展开菜单

不依赖点击状态,而是根据当前路由路径判断应该展开哪个主菜单,这种方法更符合路由驱动UI的理念,用户刷新页面或直接访问子路由时也能自动展开对应菜单。

示例:

import { useLocation } from 'react-router-dom';
import { List, ListItem, Collapse, ExpandLess, ExpandMore } from '@material-ui/core';
import { useHistory } from 'react-router-dom';

const SideMenu = () => {
  const history = useHistory();
  const location = useLocation();

  // 定义主菜单与子路由的映射关系
  const menuRouteMap = {
    dashboard: ['/dashboard', '/dashboard/overview', '/dashboard/analytics'],
    settings: ['/settings', '/settings/profile', '/settings/privacy']
  };

  // 匹配当前路由对应的主菜单key
  const currentExpandedKey = Object.keys(menuRouteMap).find(key => 
    menuRouteMap[key].includes(location.pathname)
  );

  return (
    <List>
      <ListItem button onClick={() => { 
        // 可选:点击主菜单时切换展开/收起,或直接跳转到默认子路由
        currentExpandedKey === 'dashboard' ? setExpandedKey('') : setExpandedKey('dashboard')
      }}>
        Dashboard
        {currentExpandedKey === 'dashboard' ? <ExpandLess /> : <ExpandMore />}
      </ListItem>
      <Collapse in={currentExpandedKey === 'dashboard'} unmountOnExit>
        <List component="div" disablePadding>
          <ListItem button onClick={() => history.push('/dashboard/overview')}>
            Overview
          </ListItem>
          <ListItem button onClick={() => history.push('/dashboard/analytics')}>
            Analytics
          </ListItem>
        </List>
      </Collapse>

      {/* 其他主菜单同理 */}
    </List>
  );
};

额外注意事项

  • 如果你的侧边菜单组件被包裹在Route组件内部,或者路由切换会导致菜单的父组件重新渲染,那么局部state必然会丢失,此时必须采用全局状态或路由匹配的方案。
  • 若想保留局部state,可以尝试用React.memo包裹菜单组件,避免不必要的重渲染,但这种方法仅适用于父组件重渲染不会导致菜单组件卸载的场景,可靠性不如前两种方法。

内容的提问来源于stack exchange,提问作者64Bit1990

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 10:33:20