React Router 6 是 React 生态系统中最流行的路由库,它提供了一套完整的路由解决方案,包括客户端路由、服务端路由、嵌套路由等功能。本文将详细介绍 React Router 6 的核心特性、最佳实践与实战案例,帮助你在项目中构建高效、可维护的路由系统。
1. React Router 6 核心特性
1.1 声明式路由
React Router 6 使用声明式语法定义路由,使路由配置更加清晰易懂:
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <Home />,
},
{
path: 'about',
element: <About />,
},
{
path: 'dashboard',
element: <Dashboard />,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
1.2 嵌套路由
React Router 6 简化了嵌套路由的实现,通过 children 属性定义子路由:
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <Home />,
},
{
path: 'posts',
element: <Posts />,
children: [
{
index: true,
element: <PostList />,
},
{
path: ':id',
element: <PostDetail />,
},
],
},
],
},
]);
function Layout() {
return (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/posts">Posts</Link>
</nav>
<main>
<Outlet /> {/* 渲染子路由 */}
</main>
</div>
);
}
1.3 数据加载
React Router 6 引入了 loader 函数,用于在路由渲染前加载数据:
const router = createBrowserRouter([
{
path: '/posts/:id',
element: <PostDetail />,
loader: async ({ params }) => {
const response = await fetch(`/api/posts/${params.id}`);
if (!response.ok) {
throw new Response('Not Found', { status: 404 });
}
return response.json();
},
},
]);
function PostDetail() {
const post = useLoaderData(); // 获取加载的数据
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
1.4 操作与表单处理
React Router 6 提供了 action 函数,用于处理表单提交和其他操作:
const router = createBrowserRouter([
{
path: '/posts',
element: <Posts />,
action: async ({ request }) => {
const formData = await request.formData();
const response = await fetch('/api/posts', {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Response('Error', { status: 500 });
}
return response.json();
},
},
]);
function Posts() {
const navigate = useNavigate();
const submitAction = useActionData(); // 获取操作结果
return (
<div>
{submitAction && <p>Post created successfully!</p>}
<form method="post">
<input type="text" name="title" placeholder="Title" />
<textarea name="content" placeholder="Content" />
<button type="submit">Create Post</button>
</form>
</div>
);
}
1.5 错误处理
React Router 6 提供了强大的错误处理机制,通过 errorElement 处理路由错误:
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{
path: 'posts/:id',
element: <PostDetail />,
loader: async ({ params }) => {
const response = await fetch(`/api/posts/${params.id}`);
if (!response.ok) {
throw new Response('Not Found', { status: 404 });
}
return response.json();
},
},
],
},
]);
function ErrorPage() {
const error = useRouteError();
return (
<div>
<h1>Oops!</h1>
<p>Sorry, an unexpected error has occurred.</p>
<p>{error instanceof Error ? error.message : 'Unknown error'}</p>
</div>
);
}
2. 最佳实践
2.1 路由配置管理
将路由配置集中管理,提高代码可维护性:
// src/routes/index.ts
import { createBrowserRouter } from 'react-router-dom';
import Layout from '@/components/Layout';
import Home from '@/pages/Home';
import About from '@/pages/About';
import Dashboard from '@/pages/Dashboard';
import ErrorPage from '@/pages/ErrorPage';
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Home />,
},
{
path: 'about',
element: <About />,
},
{
path: 'dashboard',
element: <Dashboard />,
loader: async () => {
// 加载仪表盘数据
const data = await fetch('/api/dashboard');
return data.json();
},
},
],
},
]);
export default router;
// src/App.tsx
import { RouterProvider } from 'react-router-dom';
import router from '@/routes';
function App() {
return <RouterProvider router={router} />;
}
export default App;
2.2 类型安全
使用 TypeScript 增强 React Router 的类型安全性:
// src/types/routes.ts
export type RouteParams = {
postId: string;
userId: string;
};
// src/routes/index.ts
import { createBrowserRouter } from 'react-router-dom';
import type { RouteObject } from 'react-router-dom';
const routes: RouteObject[] = [
{
path: '/',
element: <Layout />,
children: [
{
path: 'posts/:postId',
element: <PostDetail />,
loader: async ({ params }) => {
// TypeScript 会推断 params 的类型
const { postId } = params;
const response = await fetch(`/api/posts/${postId}`);
return response.json();
},
},
],
},
];
const router = createBrowserRouter(routes);
export default router;
// src/pages/PostDetail.tsx
import { useParams, useLoaderData } from 'react-router-dom';
import type { RouteParams } from '@/types/routes';
function PostDetail() {
// 类型安全的 params
const params = useParams<RouteParams>();
// 类型安全的加载数据
const post = useLoaderData<Post>();
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
2.3 数据加载策略
并行数据加载
使用 Promise.all 并行加载多个数据,提高性能:
const router = createBrowserRouter([
{
path: '/dashboard',
element: <Dashboard />,
loader: async () => {
const [user, posts, stats] = await Promise.all([
fetch('/api/user').then(res => res.json()),
fetch('/api/posts').then(res => res.json()),
fetch('/api/stats').then(res => res.json()),
]);
return { user, posts, stats };
},
},
]);
延迟数据加载
对于大型应用,可以使用延迟数据加载,减少初始加载时间:
const router = createBrowserRouter([
{
path: '/dashboard',
element: <Dashboard />,
loader: async () => {
// 只加载关键数据
const user = await fetch('/api/user').then(res => res.json());
return { user };
},
},
]);
// 在组件中使用 useEffect 加载非关键数据
function Dashboard() {
const { user } = useLoaderData();
const [posts, setPosts] = useState([]);
const [stats, setStats] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchAdditionalData = async () => {
const [postsData, statsData] = await Promise.all([
fetch('/api/posts').then(res => res.json()),
fetch('/api/stats').then(res => res.json()),
]);
setPosts(postsData);
setStats(statsData);
setLoading(false);
};
fetchAdditionalData();
}, []);
return (
<div>
<h1>Welcome, {user.name}!</h1>
{loading ? (
<p>Loading additional data...</p>
) : (
<>
<PostsList posts={posts} />
<Stats stats={stats} />
</>
)}
</div>
);
}
2.4 权限管理
实现基于角色的权限管理,控制路由访问:
// src/components/AuthGuard.tsx
import { Navigate, useLoaderData } from 'react-router-dom';
interface AuthGuardProps {
children: React.ReactNode;
requiredRole?: 'admin' | 'user' | 'guest';
}
function AuthGuard({ children, requiredRole = 'user' }: AuthGuardProps) {
const { user, loading } = useLoaderData<{ user: User | null; loading: boolean }>();
if (loading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" replace />;
}
if (requiredRole === 'admin' && user.role !== 'admin') {
return <Navigate to="/unauthorized" replace />;
}
return <>{children}</>;
}
// src/routes/index.ts
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
loader: async () => {
try {
const response = await fetch('/api/user');
if (response.ok) {
const user = await response.json();
return { user, loading: false };
}
} catch (error) {
console.error('Error fetching user:', error);
}
return { user: null, loading: false };
},
children: [
{
path: 'admin',
element: (
<AuthGuard requiredRole="admin">
<AdminPanel />
</AuthGuard>
),
},
{
path: 'dashboard',
element: (
<AuthGuard>
<Dashboard />
</AuthGuard>
),
},
],
},
]);
2.5 导航与状态管理
编程式导航
使用 useNavigate 钩子实现编程式导航:
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
const password = formData.get('password') as string;
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (response.ok) {
// 登录成功,导航到仪表盘
navigate('/dashboard', { replace: true });
} else {
const data = await response.json();
setError(data.message || 'Login failed');
}
} catch (error) {
setError('An error occurred');
}
};
return (
<form onSubmit={handleSubmit}>
{error && <p className="error">{error}</p>}
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
);
}
导航状态
使用 state 属性传递导航状态:
import { useNavigate, useLocation } from 'react-router-dom';
function ProductList() {
const navigate = useNavigate();
const handleProductClick = (product: Product) => {
navigate(`/products/${product.id}`, {
state: {
from: '/products',
productName: product.name,
},
});
};
return (
<div>
{products.map(product => (
<div key={product.id} onClick={() => handleProductClick(product)}>
{product.name}
</div>
))}
</div>
);
}
function ProductDetail() {
const location = useLocation();
const productName = location.state?.productName;
return (
<div>
<h1>{productName}</h1>
{/* 产品详情 */}
</div>
);
}
3. 实战案例
3.1 电商应用路由
功能需求:
- 首页展示推荐商品
- 商品列表页支持分类筛选
- 商品详情页展示商品信息和评论
- 用户中心管理个人信息和订单
- 购物车管理
路由配置:
// src/routes/index.ts
import { createBrowserRouter } from 'react-router-dom';
import Layout from '@/components/Layout';
import Home from '@/pages/Home';
import ProductList from '@/pages/ProductList';
import ProductDetail from '@/pages/ProductDetail';
import Cart from '@/pages/Cart';
import Checkout from '@/pages/Checkout';
import UserCenter from '@/pages/UserCenter';
import OrderHistory from '@/pages/OrderHistory';
import OrderDetail from '@/pages/OrderDetail';
import Login from '@/pages/Login';
import Register from '@/pages/Register';
import ErrorPage from '@/pages/ErrorPage';
import AuthGuard from '@/components/AuthGuard';
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Home />,
loader: async () => {
const response = await fetch('/api/products/recommended');
return response.json();
},
},
{
path: 'products',
children: [
{
index: true,
element: <ProductList />,
loader: async ({ request }) => {
const url = new URL(request.url);
const category = url.searchParams.get('category');
const response = await fetch(`/api/products?category=${category || ''}`);
return response.json();
},
},
{
path: ':id',
element: <ProductDetail />,
loader: async ({ params }) => {
const { id } = params;
const [product, reviews] = await Promise.all([
fetch(`/api/products/${id}`).then(res => res.json()),
fetch(`/api/products/${id}/reviews`).then(res => res.json()),
]);
return { product, reviews };
},
},
],
},
{
path: 'cart',
element: <Cart />,
},
{
path: 'checkout',
element: (
<AuthGuard>
<Checkout />
</AuthGuard>
),
},
{
path: 'user',
element: (
<AuthGuard>
<UserCenter />
</AuthGuard>
),
children: [
{
index: true,
element: <UserProfile />,
},
{
path: 'orders',
children: [
{
index: true,
element: <OrderHistory />,
loader: async () => {
const response = await fetch('/api/orders');
return response.json();
},
},
{
path: ':id',
element: <OrderDetail />,
loader: async ({ params }) => {
const { id } = params;
const response = await fetch(`/api/orders/${id}`);
return response.json();
},
},
],
},
],
},
{
path: 'login',
element: <Login />,
},
{
path: 'register',
element: <Register />,
},
],
},
]);
export default router;
3.2 博客应用路由
功能需求:
- 首页展示最新文章
- 文章列表页支持分类和标签筛选
- 文章详情页展示文章内容和评论
- 管理后台发布和编辑文章
路由配置:
// src/routes/index.ts
import { createBrowserRouter } from 'react-router-dom';
import Layout from '@/components/Layout';
import AdminLayout from '@/components/AdminLayout';
import Home from '@/pages/Home';
import PostList from '@/pages/PostList';
import PostDetail from '@/pages/PostDetail';
import CategoryList from '@/pages/CategoryList';
import TagList from '@/pages/TagList';
import AdminDashboard from '@/pages/admin/Dashboard';
import PostCreate from '@/pages/admin/PostCreate';
import PostEdit from '@/pages/admin/PostEdit';
import CategoryManage from '@/pages/admin/CategoryManage';
import TagManage from '@/pages/admin/TagManage';
import Login from '@/pages/Login';
import ErrorPage from '@/pages/ErrorPage';
import AuthGuard from '@/components/AuthGuard';
import AdminGuard from '@/components/AdminGuard';
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Home />,
loader: async () => {
const response = await fetch('/api/posts/latest');
return response.json();
},
},
{
path: 'posts',
children: [
{
index: true,
element: <PostList />,
loader: async ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const response = await fetch(`/api/posts?page=${page}`);
return response.json();
},
},
{
path: ':slug',
element: <PostDetail />,
loader: async ({ params }) => {
const { slug } = params;
const [post, comments] = await Promise.all([
fetch(`/api/posts/${slug}`).then(res => res.json()),
fetch(`/api/posts/${slug}/comments`).then(res => res.json()),
]);
return { post, comments };
},
},
],
},
{
path: 'categories',
children: [
{
index: true,
element: <CategoryList />,
loader: async () => {
const response = await fetch('/api/categories');
return response.json();
},
},
{
path: ':slug',
element: <PostList />,
loader: async ({ params, request }) => {
const { slug } = params;
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const response = await fetch(`/api/posts?category=${slug}&page=${page}`);
return response.json();
},
},
],
},
{
path: 'tags',
children: [
{
index: true,
element: <TagList />,
loader: async () => {
const response = await fetch('/api/tags');
return response.json();
},
},
{
path: ':slug',
element: <PostList />,
loader: async ({ params, request }) => {
const { slug } = params;
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const response = await fetch(`/api/posts?tag=${slug}&page=${page}`);
return response.json();
},
},
],
},
{
path: 'login',
element: <Login />,
},
],
},
{
path: '/admin',
element: (
<AdminGuard>
<AdminLayout />
</AdminGuard>
),
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <AdminDashboard />,
loader: async () => {
const [posts, categories, tags] = await Promise.all([
fetch('/api/admin/posts/count').then(res => res.json()),
fetch('/api/admin/categories/count').then(res => res.json()),
fetch('/api/admin/tags/count').then(res => res.json()),
]);
return { posts, categories, tags };
},
},
{
path: 'posts',
children: [
{
index: true,
element: <AdminPostList />,
loader: async ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const response = await fetch(`/api/admin/posts?page=${page}`);
return response.json();
},
},
{
path: 'create',
element: <PostCreate />,
},
{
path: 'edit/:id',
element: <PostEdit />,
loader: async ({ params }) => {
const { id } = params;
const response = await fetch(`/api/admin/posts/${id}`);
return response.json();
},
},
],
},
{
path: 'categories',
element: <CategoryManage />,
loader: async () => {
const response = await fetch('/api/admin/categories');
return response.json();
},
},
{
path: 'tags',
element: <TagManage />,
loader: async () => {
const response = await fetch('/api/admin/tags');
return response.json();
},
},
],
},
]);
export default router;
3.2 管理系统路由
功能需求:
- 仪表盘展示系统概览
- 用户管理
- 角色权限管理
- 内容管理
- 系统设置
路由配置:
// src/routes/index.ts
import { createBrowserRouter } from 'react-router-dom';
import AdminLayout from '@/components/AdminLayout';
import Dashboard from '@/pages/admin/Dashboard';
import UserList from '@/pages/admin/UserList';
import UserEdit from '@/pages/admin/UserEdit';
import RoleList from '@/pages/admin/RoleList';
import RoleEdit from '@/pages/admin/RoleEdit';
import ContentList from '@/pages/admin/ContentList';
import ContentEdit from '@/pages/admin/ContentEdit';
import SystemSettings from '@/pages/admin/SystemSettings';
import Login from '@/pages/Login';
import ErrorPage from '@/pages/ErrorPage';
import AdminGuard from '@/components/AdminGuard';
const router = createBrowserRouter([
{
path: '/login',
element: <Login />,
},
{
path: '/admin',
element: (
<AdminGuard>
<AdminLayout />
</AdminGuard>
),
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Dashboard />,
loader: async () => {
const [users, roles, content] = await Promise.all([
fetch('/api/admin/users/count').then(res => res.json()),
fetch('/api/admin/roles/count').then(res => res.json()),
fetch('/api/admin/content/count').then(res => res.json()),
]);
return { users, roles, content };
},
},
{
path: 'users',
children: [
{
index: true,
element: <UserList />,
loader: async ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const response = await fetch(`/api/admin/users?page=${page}`);
return response.json();
},
},
{
path: 'edit/:id',
element: <UserEdit />,
loader: async ({ params }) => {
const { id } = params;
const response = await fetch(`/api/admin/users/${id}`);
return response.json();
},
},
],
},
{
path: 'roles',
children: [
{
index: true,
element: <RoleList />,
loader: async () => {
const response = await fetch('/api/admin/roles');
return response.json();
},
},
{
path: 'edit/:id',
element: <RoleEdit />,
loader: async ({ params }) => {
const { id } = params;
const response = await fetch(`/api/admin/roles/${id}`);
return response.json();
},
},
],
},
{
path: 'content',
children: [
{
index: true,
element: <ContentList />,
loader: async ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get('page') || '1';
const response = await fetch(`/api/admin/content?page=${page}`);
return response.json();
},
},
{
path: 'edit/:id',
element: <ContentEdit />,
loader: async ({ params }) => {
const { id } = params;
const response = await fetch(`/api/admin/content/${id}`);
return response.json();
},
},
],
},
{
path: 'settings',
element: <SystemSettings />,
loader: async () => {
const response = await fetch('/api/admin/settings');
return response.json();
},
},
],
},
]);
export default router;
4. 最佳实践总结
4.1 路由设计
- 合理规划路由结构:根据应用功能模块划分路由
- 使用嵌套路由:通过嵌套路由组织相关页面
- 使用索引路由:为父路由提供默认子路由
- 使用动态路由:处理带参数的路由,如
/posts/:id
4.2 数据加载
- 使用 loader 函数:在路由渲染前加载数据
- 并行加载数据:使用
Promise.all提高数据加载效率 - 错误处理:为路由配置
errorElement处理加载错误 - 加载状态:在数据加载过程中显示加载指示器
4.3 权限管理
- 实现路由守卫:通过
AuthGuard组件控制路由访问 - 基于角色的权限:根据用户角色控制路由访问
- 登录重定向:未登录用户访问需要权限的路由时重定向到登录页
- 未授权处理:用户权限不足时显示未授权页面
4.4 性能优化
- 代码分割:使用动态导入减少初始加载时间
- 预加载路由:使用
usePreload预加载可能访问的路由 - 缓存策略:对频繁访问的数据使用缓存
- 减少重渲染:合理使用
React.memo和useMemo
4.5 开发体验
- 集中管理路由:将路由配置集中到一个文件中
- 类型安全:使用 TypeScript 确保路由参数和数据类型安全
- 模块化:将路由相关逻辑拆分为多个模块
- 测试:为路由组件和逻辑编写单元测试
总结
React Router 6 提供了一套完整、灵活的路由解决方案,通过本文介绍的核心特性、最佳实践与实战案例,你可以:
- 构建声明式路由:使用
createBrowserRouter和RouterProvider定义路由 - 实现嵌套路由:通过
children属性组织路由结构 - 优化数据加载:使用
loader函数在路由渲染前加载数据 - 处理表单提交:使用
action函数处理表单提交和其他操作 - 实现权限管理:通过路由守卫控制路由访问
- 优化用户体验:使用编程式导航和导航状态
随着 React 生态系统的不断发展,React Router 也在持续改进,为开发者提供更好的路由解决方案。掌握 React Router 6 的最佳实践,将帮助你构建更加高效、可维护的 React 应用。