React17+react-router-dom@5.2.0如何给子路由传默认布局组件
问题描述
我制作了一个AdminDefaultLayout管理员默认布局组件,希望将其应用于所有管理员路由组件。但由于使用React 17和react-router-dom@5.2.0版本,无法使用react-router-dom@6+中的Outlet特性。尝试相关写法后,页面仅显示AdminDefaultLayout组件,Dashboard组件不显示,请求指导解决。
期望写法
Route.js 期望结构
<BrowserRouter> <Routes> <Route path="/admin/dashboard" element={AdminDefaultLayout}> <Route path="/admin/dashboard" element={Dashboard} /> <Route path="/admin/category" element={Category} /> </Route> </Routes> </BrowserRouter>
AdminDefaultLayout.jsx 期望结构
<> <div className="container"> <Header /> <Sidebar /> <main> <Outlet /> </main> <Footer /> </div> </>
当前环境依赖
{"name": "magicbox","version": "1.3.0","private": true,"dependencies": {"axios": "^1.5.1","google-map-react": "^1.1.7","http-proxy-middleware": "^0.19.2","isomorphic-fetch": "^3.0.0","lax.js": "^1.2.5","react": "^17.0.2","react-accessible-accordion": "^3.3.4","react-countup": "^4.4.0","react-dom": "^17.0.2","react-modal-video": "^1.2.8","react-owl-carousel3": "^2.2.5","react-router-dom": "^5.2.0","react-scripts": "^5.0.1","react-visibility-sensor": "^5.1.1","sass": "^1.51.0"}}
当前代码写法
route.jsx
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import HomeFour from "./components/pages/HomeFour"; import Login from "./components/pages/Login"; import AdminDefaultLayout from "./components/AdminDefaultLayout"; import Dashboard from "./components/pages/admin/Dashboard"; const renderNavigation = () => { if (!(window.location.pathname === '/login' || window.location.pathname === '/signup' || window.location.pathname === '/coming-soon' || window.location.pathname === '/error-404' || window.location.pathname === '/admin/dashboard'|| window.location.pathname === '/admin/category') ) { return <Navigation />; } } const AppRouter = () => { const existToken = localStorage.getItem('ACCESS_TOKEN'); return ( <> { existToken ? ( <Router> <Switch> <Route path="/admin/dashboard" render={(props) => <AdminDefaultLayout><Dashboard {...props} /></AdminDefaultLayout>}/> </Switch> </Router> ) : ( <Router> {renderNavigation()} <Switch> <Route path="/login" exact component={Login} /> <Route path="/" exact component={HomeFour} /> </Switch> </Router> ) } </> ) } export default AppRouter;
AdminDefaultLayout.jsx
<> <div className="container"> <Header /> <Sidebar /> <main> <Switch></Switch> </main> <Footer /> </div> </>
注:已修正错误,package.json中无react-router依赖。
解决方案
在react-router-dom@5.x中没有Outlet,可以通过以下两种方式实现布局嵌套:
方法一:利用组件children属性传递内容
这是最直接的方式,修改AdminDefaultLayout接收并渲染子组件:
1. 修改AdminDefaultLayout.jsx
import React from 'react'; import Header from './Header'; // 确保路径正确 import Sidebar from './Sidebar'; import Footer from './Footer'; const AdminDefaultLayout = ({ children }) => { return ( <> <div className="container"> <Header /> <Sidebar /> <main> {children} {/* 替代Outlet渲染子组件 */} </main> <Footer /> </div> </> ); }; export default AdminDefaultLayout;
2. 调整路由写法
将需要嵌套的组件作为AdminDefaultLayout的子元素传入,同时扩展管理员路由:
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import HomeFour from "./components/pages/HomeFour"; import Login from "./components/pages/Login"; import AdminDefaultLayout from "./components/AdminDefaultLayout"; import Dashboard from "./components/pages/admin/Dashboard"; import Category from "./components/pages/admin/Category"; // 导入Category组件 import Navigation from "./components/Navigation"; // 确保导入Navigation const renderNavigation = () => { const excludedPaths = ['/login', '/signup', '/coming-soon', '/error-404', '/admin/dashboard', '/admin/category']; if (!excludedPaths.includes(window.location.pathname)) { return <Navigation />; } } const AppRouter = () => { const existToken = localStorage.getItem('ACCESS_TOKEN'); return ( <> { existToken ? ( <Router> <Switch> {/* 所有管理员路由用AdminDefaultLayout包裹 */} <Route path="/admin/dashboard" render={(props) => ( <AdminDefaultLayout> <Dashboard {...props} /> </AdminDefaultLayout> )}/> <Route path="/admin/category" render={(props) => ( <AdminDefaultLayout> <Category {...props} /> </AdminDefaultLayout> )}/> </Switch> </Router> ) : ( <Router> {renderNavigation()} <Switch> <Route path="/login" exact component={Login} /> <Route path="/" exact component={HomeFour} /> </Switch> </Router> ) } </> ) } export default AppRouter;
方法二:使用react-router-dom v5的嵌套路由
通过父路由匹配/admin前缀,在布局内部渲染子路由:
1. 修改AdminDefaultLayout.jsx
在布局内部添加Switch和子路由:
import React from 'react'; import { Route, Switch } from 'react-router-dom'; import Header from './Header'; import Sidebar from './Sidebar'; import Footer from './Footer'; import Dashboard from './pages/admin/Dashboard'; import Category from './pages/admin/Category'; const AdminDefaultLayout = () => { return ( <> <div className="container"> <Header /> <Sidebar /> <main> <Switch> {/* 子路由path无需完整路径,父路由已匹配/admin前缀 */} <Route path="/admin/dashboard" component={Dashboard} /> <Route path="/admin/category" component={Category} /> </Switch> </main> <Footer /> </div> </> ); }; export default AdminDefaultLayout;
2. 调整路由写法
父路由匹配所有/admin开头的路径,渲染布局组件:
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import HomeFour from "./components/pages/HomeFour"; import Login from "./components/pages/Login"; import AdminDefaultLayout from "./components/AdminDefaultLayout"; import Navigation from "./components/Navigation"; const renderNavigation = () => { const excludedPaths = ['/login', '/signup', '/coming-soon', '/error-404', '/admin/dashboard', '/admin/category']; if (!excludedPaths.includes(window.location.pathname)) { return <Navigation />; } } const AppRouter = () => { const existToken = localStorage.getItem('ACCESS_TOKEN'); return ( <> { existToken ? ( <Router> <Switch> {/* 父路由匹配所有/admin开头的路径 */} <Route path="/admin" component={AdminDefaultLayout} /> </Switch> </Router> ) : ( <Router> {renderNavigation()} <Switch> <Route path="/login" exact component={Login} /> <Route path="/" exact component={HomeFour} /> </Switch> </Router> ) } </> ) } export default AppRouter;
额外优化建议
当前renderNavigation直接操作window.location.pathname,在React中建议用useLocation钩子获取路径,避免直接操作DOM:
import { useLocation } from 'react-router-dom'; const NavigationWrapper = () => { const location = useLocation(); const excludedPaths = ['/login', '/signup', '/coming-soon', '/error-404', '/admin/dashboard', '/admin/category']; if (!excludedPaths.includes(location.pathname)) { return <Navigation />; } return null; }
然后在路由中替换{renderNavigation()}为<NavigationWrapper />,确保在Router内部使用钩子。
内容的提问来源于stack exchange,提问作者Muhammad Taha
相关产品推荐
相关产品推荐

