如何在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
相关产品推荐
相关产品推荐

