ReactJS:将Topbar与Sidebar合并为单一导航布局
问题描述
我正在构建一个React应用,包含两种布局:
- 未登录时:使用无Sidebar和Topbar的布局
- 登录后(通过context判断状态):使用包含Topbar和Sidebar的布局
当前遇到的问题:登录状态下,Topbar和Sidebar作为独立组件渲染,导致Dashboard等页面组件被挤到页面底部。需要将这两个栏合并为统一布局结构,让页面内容显示在布局的空白区域。
解决方案
一、重构布局与栏组件结构
问题核心是当前组件未遵循SB Admin 2的DOM结构规范,容器嵌套错误,且路由并行渲染布局与页面组件导致排版混乱,以下是修复步骤:
1. 调整Topbar组件结构
移除多余的外层容器,保留Topbar本身的导航结构:
// Topbar.jsx import "startbootstrap-sb-admin-2-master/css/sb-admin-2.min.css"; import { useAuth } from "../../hooks/auth"; export const TopBar = () => { const user = useAuth().user; const { logout } = useAuth(); const handleSubmit = async (e) => { e.preventDefault(); logout(); }; return ( <nav className="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow"> <button id="sidebarToggleTop" className="btn btn-link d-md-none rounded-circle mr-3" > <i className="fa fa-bars"></i> </button> <form className="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search"> <div className="input-group"> <input type="text" className="form-control bg-light border-0 small" placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2" /> <div className="input-group-append"> <button className="btn btn-primary" type="button"> <i className="fa-solid fa-magnifying-glass"></i> </button> </div> </div> </form> <ul className="navbar-nav ml-auto"> <li className="nav-item dropdown no-arrow mx-1"> <div className="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" > <i className="fas fa-bell fa-fw"></i> <span className="badge badge-danger badge-counter">3+</span> </div> </li> <div className="topbar-divider d-none d-sm-block" /> <li className="nav-item dropdown no-arrow"> <div className="nav-link" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" > <span className="mr-2 d-none d-lg-inline text-gray-600 small"> Hi, {user}! </span> <i className="fa-solid fa-user"></i> </div> </li> <li className="nav-item dropdown no-arrow"> <div className="nav-link" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" > <button className="btn btn-secondary btn-circle" type="button" onClick={handleSubmit} > <i className="fa-solid fa-arrow-right-from-bracket"></i> </button> </div> </li> </ul> </nav> ); };
2. 调整Navbar组件结构
保留核心Sidebar列表,移除多余的外层容器嵌套:
// Navbar.jsx import "startbootstrap-sb-admin-2-master/css/sb-admin-2.min.css"; import { useAuth } from "../../hooks/auth"; export const Navbar = () => { return ( <ul className="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar" > <hr className="sidebar-divider my-0" /> <li className="nav-item active"> <a className="nav-link" href="/auth/dashboard"> <i className="fas fa-fw fa-tachometer-alt"></i> <span>Dashboard</span> </a> </li> <hr className="sidebar-divider" /> <div className="sidebar-heading">Interface</div> <li className="nav-item"> <a className="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo" > <i className="fas fa-fw fa-cog"></i> <span>Components</span> </a> <div id="collapseTwo" className="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar" > <div className="bg-white py-2 collapse-inner rounded"> <h6 className="collapse-header">Custom Components:</h6> <a className="collapse-item" href="buttons.html">Buttons</a> <a className="collapse-item" href="cards.html">Cards</a> </div> </div> </li> <li className="nav-item"> <a className="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities" aria-expanded="true" aria-controls="collapseUtilities" > <i className="fas fa-fw fa-wrench"></i> <span>Utilities</span> </a> <div id="collapseUtilities" className="collapse" aria-labelledby="headingUtilities" data-parent="#accordionSidebar" > <div className="bg-white py-2 collapse-inner rounded"> <h6 className="collapse-header">Custom Utilities:</h6> <a className="collapse-item" href="utilities-color.html">Colors</a> <a className="collapse-item" href="utilities-border.html">Borders</a> <a className="collapse-item" href="utilities-animation.html">Animations</a> <a className="collapse-item" href="utilities-other.html">Other</a> </div> </div> </li> <hr className="sidebar-divider" /> <div className="sidebar-heading">Addons</div> <li className="nav-item"> <a className="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapsePages" aria-expanded="true" aria-controls="collapsePages" > <i className="fas fa-fw fa-folder"></i> <span>Pages</span> </a> <div id="collapsePages" className="collapse" aria-labelledby="headingPages" data-parent="#accordionSidebar" > <div className="bg-white py-2 collapse-inner rounded"> <h6 className="collapse-header">Login Screens:</h6> <a className="collapse-item" href="/">Login</a> <a className="collapse-item" href="register.html">Register</a> <a className="collapse-item" href="forgot-password.html">Forgot Password</a> <div className="collapse-divider"></div> <h6 className="collapse-header">Other Pages:</h6> <a className="collapse-item" href="404.html">404 Page</a> <a className="collapse-item" href="blank.html">Blank Page</a> </div> </div> </li> <li className="nav-item"> <a className="nav-link" href="charts.html"> <i className="fas fa-fw fa-chart-area"></i> <span>Charts</span> </a> </li> <li className="nav-item"> <a className="nav-link" href="tables.html"> <i className="fas fa-fw fa-table"></i> <span>Tables</span> </a> </li> <hr className="sidebar-divider d-none d-md-block" /> <div className="text-center d-none d-md-inline"> <button className="rounded-circle border-0" id="sidebarToggle" /> </div> </ul> ); };
3. 重构ProtectedLayout组件
按照SB Admin 2的标准结构组织布局,将页面内容通过Outlet插入到指定区域:
// ProtectedLayout.jsx import { Outlet } from "react-router"; import { Navbar } from "../components/common/Navbar"; import { TopBar } from "../components/common/Topbar"; export const ProtectedLayout = () => { return ( <div id="wrapper"> {/* Sidebar */} <Navbar /> {/* Content Wrapper */} <div id="content-wrapper" className="d-flex flex-column"> {/* Main Content */} <div id="content"> {/* Topbar */} <TopBar /> {/* Page Content */} <div className="container-fluid"> <Outlet /> </div> </div> </div> </div> ); };
二、路由配置优化
利用React Router的嵌套路由特性,简化路由结构,避免重复渲染布局组件:
// App.js import React from "react"; import { Route, Routes } from "react-router-dom"; import { LoginLayout } from "./layout/LoginLayout"; import { ProtectedLayout } from "./layout/ProtectedLayout"; import { ProtectRoutes } from "./hooks/protectedRoute"; import { Login } from "./pages/LoginPage"; import { Dashboard } from "./pages/DashboardPage"; import { Hi } from "./pages/HiPage"; import { About } from "./pages/AboutPage"; export default function App() { return ( <Routes> {/* 未登录路由 */} <Route element={<LoginLayout />}> <Route path="/" element={<Login />} /> </Route> {/* 已登录保护路由 */} <Route path="/auth" element={<ProtectRoutes />}> <Route element={<ProtectedLayout />}> <Route path="dashboard" element={<Dashboard />} /> <Route path="hi" element={<Hi />} /> <Route path="about" element={<About />} /> </Route> </Route> </Routes> ); }
路由优化说明
- 让
ProtectedLayout作为/auth下所有子路由的父布局,页面组件通过Outlet自动嵌入到布局的内容区域 - 简化路由路径写法,子路由继承父路由前缀,代码更简洁易维护
- 避免了原代码中并行渲染布局与页面组件的错误写法
内容的提问来源于stack exchange,提问作者cameronErasmus
相关产品推荐
相关产品推荐

