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

如何在React.js中将受保护路由纳入数组实现动态导航?

解决方案

1. 给菜单加权限标记

先修改menuOptions数组,给每个选项加requiresAuth字段区分登录/未登录可见的菜单;给Logout加isLogout标记,因为它需要执行登出逻辑而非单纯跳转:

const menuOptions = [
  { label: "Home", path: "/", requiresAuth: false },
  { label: "Login", path: "/login", requiresAuth: false },
  { label: "Register", path: "/register", requiresAuth: false },
  { label: "Dashboard", path: "/dashboard", requiresAuth: true },
  { label: "Logout", path: "/logout", requiresAuth: true, isLogout: true },
];

2. 根据登录状态过滤菜单

在组件内计算当前用户可见的菜单列表:

const filteredMenuOptions = menuOptions.filter(opt => {
  // 未登录只显示不需要权限的选项;登录后显示需要权限的选项(包括Logout)
  return !user ? !opt.requiresAuth : opt.requiresAuth;
});

3. 统一移动端和桌面端渲染逻辑

把原来硬编码的菜单替换成循环过滤后的数组,同时处理Logout的特殊点击事件:

移动端菜单修改

<Menu
  id="menu-appbar"
  anchorEl={anchorEl}
  anchorOrigin={{ vertical: "top", horizontal: "right" }}
  keepMounted
  transformOrigin={{ vertical: "top", horizontal: "right" }}
  open={open}
  onClose={() => setAnchorEl(null)}
>
  {filteredMenuOptions.map((opt) => (
    <MenuItem
      key={opt.label}
      onClick={() => {
        if (opt.isLogout) {
          logout();
          navigate("/"); // 登出后跳转首页
        } else {
          handleMenuSelect(opt.path);
        }
        setAnchorEl(null); // 点击后自动关闭菜单
      }}
    >
      {opt.label}
    </MenuItem>
  ))}
</Menu>

桌面端菜单修改

<nav>
  {filteredMenuOptions.map((opt) => (
    <React.Fragment key={opt.label}>
      {opt.isLogout ? (
        <Link
          to="#"
          onClick={(e) => {
            e.preventDefault(); // 阻止默认跳转行为
            logout();
            navigate("/");
          }}
        >
          <a>{opt.label}</a>
        </Link>
      ) : (
        <NavLink
          to={opt.path}
          className={({ isActive }) =>
            isActive ? classes.activeLink : classes.inactiveLink
          }
        >
          {opt.label}
        </NavLink>
      )}
    </React.Fragment>
  ))}
</nav>

4. 完善菜单选择逻辑

确保移动端点击菜单后自动关闭弹窗:

const handleMenuSelect = (pageURL) => {
  navigate(pageURL);
  setAnchorEl(null);
};

完整修改后的组件代码

import React, { useState } from "react";
import { Link, NavLink, useNavigate } from "react-router-dom";
import { useAuthState } from "react-firebase-hooks/auth";
import { auth, logout } from "../../firebase";
import { makeStyles } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import Menu from "@material-ui/core/Menu";
import { useTheme } from "@material-ui/core/styles";
import useMediaQuery from "@material-ui/core/useMediaQuery";

const useStyles = makeStyles((theme) => ({
  title: {
    flexGrow: 1,
  },
  appbar: {
    // background: 'none',
  },
  inactiveLink: {
    color: "white",
    padding: theme.spacing(1),
    fontSize: "1.5rem",
  },
  activeLink: {
    color: "black",
    padding: theme.spacing(1),
    fontSize: "1.5rem",
    background: "#bfbfbf",
  },
}));

function Navigation() {
  const classes = useStyles();
  const [anchorEl, setAnchorEl] = useState(null);
  const theme = useTheme();
  const isMobile = useMediaQuery(theme.breakpoints.down("md"));
  const [user] = useAuthState(auth);
  const navigate = useNavigate();

  const open = Boolean(anchorEl);

  // 带权限控制的菜单选项
  const menuOptions = [
    { label: "Home", path: "/", requiresAuth: false },
    { label: "Login", path: "/login", requiresAuth: false },
    { label: "Register", path: "/register", requiresAuth: false },
    { label: "Dashboard", path: "/dashboard", requiresAuth: true },
    { label: "Logout", path: "/logout", requiresAuth: true, isLogout: true },
  ];

  // 过滤出当前用户可见的菜单
  const filteredMenuOptions = menuOptions.filter(opt => {
    return !user ? !opt.requiresAuth : opt.requiresAuth;
  });

  const handleMenuSelect = (pageURL) => {
    navigate(pageURL);
    setAnchorEl(null);
  };

  const handleMenu = (event) => {
    setAnchorEl(event.currentTarget);
  };

  return (
    <>
      <AppBar
        className={classes.appbar}
        position="fixed"
        elevation={0}
        color="primary"
      >
        <Toolbar>
          <Typography variant="h4" className={classes.title}>
            The Football App
          </Typography>
          <Typography variant="h6" className={classes.title}>
            All you ever wanted to know about Football!
          </Typography>
          {isMobile ? (
            <>
              <IconButton
                aria-label="menu"
                aria-controls="menu-appbar"
                aria-haspopup="true"
                onClick={handleMenu}
                color="inherit"
              >
                <MenuIcon />
              </IconButton>
              <Menu
                id="menu-appbar"
                anchorEl={anchorEl}
                anchorOrigin={{ vertical: "top", horizontal: "right" }}
                keepMounted
                transformOrigin={{ vertical: "top", horizontal: "right" }}
                open={open}
                onClose={() => setAnchorEl(null)}
              >
                {filteredMenuOptions.map((opt) => (
                  <MenuItem
                    key={opt.label}
                    onClick={() => {
                      if (opt.isLogout) {
                        logout();
                        navigate("/");
                      } else {
                        handleMenuSelect(opt.path);
                      }
                    }}
                  >
                    {opt.label}
                  </MenuItem>
                ))}
              </Menu>
            </>
          ) : (
            <nav>
              {filteredMenuOptions.map((opt) => (
                <React.Fragment key={opt.label}>
                  {opt.isLogout ? (
                    <Link
                      to="#"
                      onClick={(e) => {
                        e.preventDefault();
                        logout();
                        navigate("/");
                      }}
                    >
                      <a>{opt.label}</a>
                    </Link>
                  ) : (
                    <NavLink
                      to={opt.path}
                      className={({ isActive }) =>
                        isActive ? classes.activeLink : classes.inactiveLink
                      }
                    >
                      {opt.label}
                    </NavLink>
                  )}
                </React.Fragment>
              ))}
            </nav>
          )}
        </Toolbar>
      </AppBar>
    </>
  );
}

export default Navigation;

核心优势

  • 用requiresAuth字段统一管理菜单权限,后续新增菜单只需添加字段,无需修改渲染逻辑
  • 移动端和桌面端共用同一套菜单数据源,避免重复代码
  • 单独处理Logout的登出逻辑,符合业务需求
  • 修复了NavLink的样式绑定问题,激活状态能正确显示样式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 15:33:34