4、前端基础(三):React Router路由配置、嵌套路由、导航守卫
好,咱们今天聊聊 React Router。说实话,这是前端单页应用里最绕不开的一个库。你想想看,没有路由,页面怎么跳转?用户怎么在不同视图之间切换?我刚开始用 React 写项目时,第一件事就是配路由。今天我把这块掰开揉碎讲清楚。
4.1 路由基础配置
先说说最基础的。React Router 目前主流是 v6 版本,跟 v5 差别挺大。我个人习惯直接用 v6,因为 API 更简洁,嵌套路由也更直观。
安装很简单:
npm install react-router-dom
然后在项目入口文件里,用 BrowserRouter 把整个应用包起来:
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Main />
</BrowserRouter>
);
}
这里要注意,BrowserRouter 用的是 HTML5 的 history API,URL 看起来是正常的路径。还有一种 HashRouter,URL 里带 # 号,我一般只在静态部署时用。
接下来配置路由表。v6 里用 Routes 和 Route:
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
);
}
嗯,这里有个细节:v6 里 component 属性改成了 element,直接传 JSX。我之前从 v5 迁移时踩过这个坑,一直报错找不到组件。
4.2 嵌套路由
嵌套路由是实际项目里用得最多的。比如一个后台管理系统,左侧是菜单,右侧是内容区。点击菜单切换右侧内容,左侧菜单不动。这就是典型的嵌套路由场景。
v6 里实现嵌套路由有两种方式。第一种是在路由配置里直接嵌套:
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="users" element={<Users />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
注意看,DashboardLayout 组件里需要有一个 <Outlet /> 占位符。子路由的内容会渲染在这个位置。
import { Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<Sidebar />
<main>
<Outlet /> {/* 子路由内容显示在这里 */}
</main>
</div>
);
}
第二种方式是用 useRoutes 钩子,适合把路由配置抽成单独文件:
import { useRoutes } from 'react-router-dom';
const routeConfig = [
{
path: '/dashboard',
element: <DashboardLayout />,
children: [
{ index: true, element: <DashboardHome /> },
{ path: 'users', element: <Users /> },
{ path: 'settings', element: <Settings /> },
],
},
];
function AppRoutes() {
return useRoutes(routeConfig);
}
我个人更推荐第二种。为什么?因为路由配置多了以后,单独维护一个文件,清晰很多。我在项目中遇到过,所有路由堆在 App.js 里,后来改一个路径要找半天。
users 会自动拼接成 /dashboard/users。不要写成 /users,否则会匹配到根路径下的 users。
4.3 导航守卫
导航守卫,说白了就是「用户能不能访问这个页面」。比如未登录用户不能进个人中心,管理员才能进后台。
React Router v6 没有像 Vue Router 那样的 beforeEach 钩子。但我们可以自己封装一个高阶组件或者用 Navigate 组件来实现。
最简单的做法:
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = localStorage.getItem('token');
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
然后在路由里包裹需要保护的组件:
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
}
>
{/* 子路由 */}
</Route>
这里有个坑:replace 属性很重要。如果不加,用户点浏览器的返回按钮,会回到登录页,然后又重定向到 dashboard,形成死循环。我曾经因为这个被测试小姐姐追着改 bug。
更复杂的场景,比如角色权限控制:
function RoleGuard({ children, allowedRoles }) {
const userRole = getUserRole(); // 从 context 或 store 获取
if (!allowedRoles.includes(userRole)) {
return <Navigate to="/403" replace />;
}
return children;
}
// 使用
<Route
path="/admin"
element={
<RoleGuard allowedRoles={['admin', 'superadmin']}>
<AdminPanel />
</RoleGuard>
}
/>
4.4 编程式导航
有时候需要在代码里跳转,比如登录成功后跳回首页。v6 里用 useNavigate 钩子:
import { useNavigate } from 'react-router-dom';
function LoginPage() {
const navigate = useNavigate();
const handleLogin = async () => {
await login();
navigate('/dashboard', { replace: true });
};
return (
<button onClick={handleLogin}>登录</button>
);
}
这里 replace: true 的意思是替换当前历史记录,而不是新增一条。登录成功后用户按返回键不会回到登录页,体验更好。
还可以传 state:
navigate('/profile', { state: { from: 'settings' } });
目标页面用 useLocation 接收:
import { useLocation } from 'react-router-dom';
function Profile() {
const location = useLocation();
console.log(location.state); // { from: 'settings' }
}
4.5 路由懒加载
项目大了以后,所有页面打包成一个 JS 文件,首屏加载会很慢。这时候需要路由懒加载。
React 提供了 lazy 和 Suspense:
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Users = lazy(() => import('./pages/Users'));
function AppRoutes() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/users" element={<Users />} />
</Routes>
</Suspense>
);
}
注意,lazy 只支持默认导出。如果你的组件是命名导出,需要转一下:
const Dashboard = lazy(() =>
import('./pages/Dashboard').then(module => ({ default: module.Dashboard }))
);
核心要点总结:
- v6 用
Routes+Route,component改成element - 嵌套路由用
<Outlet />占位 - 导航守卫封装成组件,用
<Navigate />重定向 - 编程式导航用
useNavigate钩子 - 懒加载用
lazy+Suspense
好了,路由这块就这些。说白了就是配置路径、控制访问、按需加载。你把这些搞明白,大部分项目的路由需求都能应付。下一节我们聊聊状态管理,那又是另一个有意思的话题。