React Router 是 React 应用中最主流的路由解决方案,当前主版本为 v6.x。v6 相比 v5 进行了大量简化与增强,核心 API 更加直观,支持嵌套路由、数据加载、路由守卫等企业级需求。
12.1 路由基础配置与嵌套路由
安装与核心组件
npm install react-router-dom
React Router v6 的核心组件:
BrowserRouter:使用 HTML5 history API 的路由容器(开发常用)HashRouter:使用 URL hash 的路由容器Routes:路由匹配区域,替代 v5 的SwitchRoute:单个路由规则Link/NavLink:声明式导航Outlet:嵌套路由的子路由出口
基础路由配置:
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function Home() { return <h2>首页</h2>; }
function About() { return <h2>关于我们</h2>; }
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
element 属性替代 v5 的 component / render,可以传入任意 JSX。
嵌套路由
在父路由组件中使用 Outlet 渲染匹配的子路由。这使得布局复用变得极其简单。
// 布局组件
function DashboardLayout() {
return (
<div>
<h1>控制台</h1>
<nav>
<Link to="stats">统计</Link>
<Link to="settings">设置</Link>
</nav>
<Outlet /> {/* 子路由内容将在此处渲染 */}
</div>
);
}
function Stats() { return <div>统计页面</div>; }
function Settings() { return <div>设置页面</div>; }
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Stats />} /> {/* index 路由:默认子路由 */}
<Route path="stats" element={<Stats />} />
<Route path="settings" element={<Settings />} />
</Route>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
);
}
index路由:当匹配父路由路径时,自动渲染的默认子路由。- 子路由路径相对于父路由,无需写完整路径
/dashboard/stats。
12.2 动态路由、路由参数、查询参数
动态路由与 useParams
定义带参数的路由,使用 :参数名 语法,通过 useParams() 获取参数值。
// 路由定义
<Route path="/users/:userId" element={<UserProfile />} />
// 组件内获取参数
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
return <div>用户ID: {userId}</div>;
}
查询参数(Query String)
v6 不再默认解析查询参数,需使用 useSearchParams 或 URLSearchParams。
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const keyword = searchParams.get('q') || '';
const updateKeyword = (newKeyword) => {
setSearchParams({ q: newKeyword });
};
return (
<div>
<input value={keyword} onChange={e => updateKeyword(e.target.value)} />
<p>搜索词:{keyword}</p>
</div>
);
}
setSearchParams 会更新 URL 查询参数并触发导航。
可选参数与通配符
v6 不直接支持可选参数,可用多路由或 useLocation 自行解析。通配符使用 * 匹配任意路径。
<Route path="/files/*" element={<FileViewer />} />
在 FileViewer 内通过 useParams() 的 * 获取剩余路径,可用于文件系统式路由。
12.3 编程式导航与路由跳转
除了 <Link>,经常需要在事件处理、异步操作后跳转页面。使用 useNavigate hook。
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleLogin = async () => {
await loginAPI();
navigate('/dashboard', { replace: true }); // 替换当前历史记录
};
return <button onClick={handleLogin}>登录</button>;
}
navigate 的第一个参数也是路由路径,第二参数可以传递状态:
navigate('/result', { state: { from: 'login' } });
目标组件通过 useLocation 读取:
import { useLocation } from 'react-router-dom';
function ResultPage() {
const location = useLocation();
const from = location.state?.from;
return <div>来自:{from}</div>;
}
<Navigate> 组件:用于声明式的重定向,适合条件渲染。
import { Navigate } from 'react-router-dom';
function ProtectedPage({ isLoggedIn }) {
if (!isLoggedIn) {
return <Navigate to="/login" replace />;
}
return <Dashboard />;
}
12.4 路由守卫与权限控制实现
React Router 没有内置的路由守卫机制,但可以利用组件逻辑实现鉴权控制。常见方式:封装一个 <ProtectedRoute> 组件。
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isLoggedIn = useAuth(); // 自定义 hook 获取登录状态
const location = useLocation();
if (!isLoggedIn) {
// 将当前路径传给登录页,登录后可跳回
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
在路由中使用:
<Routes>
<Route path="/dashboard" element={
<ProtectedRoute>
<DashboardLayout />
</ProtectedRoute>
}>
{/* 子路由 */}
</Route>
<Route path="/login" element={<Login />} />
</Routes>
对于基于角色的权限,可以在 ProtectedRoute 内校验角色:
function AdminRoute({ children }) {
const { user } = useAuth();
if (!user || user.role !== 'admin') {
return <Navigate to="/403" replace />;
}
return children;
}
更复杂的权限可结合路由配置数组动态生成路由,避免重复书写保护组件。
12.5 路由懒加载与代码分割
对于大型应用,按路由拆分代码是提升首屏加载速度的关键。使用 React.lazy + Suspense 实现。
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Suspense 包裹 <Routes> 或单个懒加载组件,在动态导入过程中显示 fallback。v6 中 React.lazy 需要默认导出组件。
优化技巧:将懒加载粒度控制在路由级别,避免过多的 loading 闪烁。
12.6 数据路由:Loader / Action 数据加载与提交
React Router v6.4+ 引入了数据路由(Data Router),核心是 createBrowserRouter 和 RouterProvider,提供路由级别的数据加载(Loader)和表单提交处理(Action),支持数据预获取、错误处理、乐观更新等。
创建数据路由
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import RootLayout from './layouts/Root';
import HomePage, { loader as homeLoader } from './pages/Home';
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />,
children: [
{ index: true, element: <HomePage />, loader: homeLoader },
{ path: 'products/:id', element: <ProductPage />, loader: productLoader },
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
Loader:组件渲染前获取数据
每个路由可以定义一个 loader 函数,在路由匹配时并行调用(数据预加载)。组件通过 useLoaderData 获取数据。
// productLoader.js
export async function loader({ params }) {
const res = await fetch(`/api/products/${params.id}`);
return res.json();
}
// ProductPage.jsx
import { useLoaderData } from 'react-router-dom';
function ProductPage() {
const product = useLoaderData();
return <div>{product.name}</div>;
}
优势:
- 数据请求在路由跳转时即开始,无需等组件挂载再请求,体验更流畅。
- 支持嵌套路由的并行数据加载。
- 自动处理加载状态和错误(配合
errorElement)。
Action:处理表单提交等副作用
action 函数类似 loader,但用于处理 POST / PUT / DELETE 等请求。组件通过 <Form> 组件提交。
import { Form, useActionData } from 'react-router-dom';
// 路由定义
{
path: 'login',
element: <LoginPage />,
action: async ({ request }) => {
const formData = await request.formData();
const user = await loginAPI(Object.fromEntries(formData));
return redirect('/dashboard');
},
}
// LoginPage.jsx
function LoginPage() {
const actionData = useActionData();
return (
<Form method="post">
<input name="username" />
<input name="password" type="password" />
<button type="submit">登录</button>
{actionData?.error && <p>{actionData.error}</p>}
</Form>
);
}
<Form> 组件会阻止默认表单提交,调用路由的 action。useActionData 获取 action 返回的数据(用于错误提示等)。
错误处理与悬停状态
可以给每个路由定义 errorElement,当 loader 或 action 抛出异常时自动渲染错误界面。
{
path: 'products/:id',
element: <ProductPage />,
loader: productLoader,
errorElement: <ErrorPage />,
}
还可以使用 useRouteError 获取错误详情。结合 Suspense 或内部 <Await> 处理异步组件不会冲突,因为数据路由本身不依赖 <Suspense>。
传统路由 vs 数据路由的选择
- 如果项目仅需基本页面跳转,传统
<BrowserRouter>+<Routes>足够。 - 如果涉及大量数据加载与表单处理、追求更好的性能和用户体验,推荐使用数据路由。
数据路由是现代 React Router 的主推方向,Next.js、Remix 等框架也借鉴了类似思想。
以上是 React Router v6 最核心、最实用的内容。掌握这些足以应对大多数业务场景,进一步优化可查阅官方文档了解 useFetcher、defer、Await 等高级特性。