关于 react-router
面向 React 开发者的 React Router 构建参考技能,涵盖导航、路由参数、嵌套路由、数据加载及常见路由问题。
主要功能
- 01链接与编程式导航
- 02路由参数与查询参数
- 03嵌套与受保护路由
- 04Loader 与错误边界
- 05路由实践与故障排查
适用场景
- 01配置 React 应用中的导航
- 02构建共享布局的嵌套页面
- 03通过路由 Loader 加载数据
- 04处理受保护路由和路由错误
MCP 相关
该技能提及使用 `mcp__documentation__fetch_docs` 获取 React Router 文档。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill react-router安装后在 Claude Code 中输入/react-router确认已加载。
附带文件advanced.md
详细介绍
React Router
深入了解:使用
mcp__documentation__fetch_docs,技术:react,主题:router,获取有关 React Router v6+ 模式和数据加载的完整文档。
完整参考:请参阅 advanced.md,了解数据加载(v6.4+)、操作、代码拆分、路由配置、错误处理、滚动恢复、模态路由和 TypeScript 集成。
基本设置
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
导航
Link 组件
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Basic link */}
<Link to="/">Home</Link>
{/* NavLink with active styling */}
<NavLink
to="/about"
className={({ isActive, isPending }) =>
isActive ? 'active' : isPending ? 'pending' : ''
}
>
About
</NavLink>
{/* With state */}
<Link to="/dashboard" state={{ from: 'home' }}>
Dashboard
</Link>
{/* Replace instead of push */}
<Link to="/login" replace>
Login
</Link>
</nav>
);
}
编程式导航
import { useNavigate, useLocation } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = async () => {
await login();
// Navigate with state
navigate('/dashboard', {
state: { from: location },
replace: true,
});
};
const handleBack = () => {
navigate(-1); // Go back
};
return (
<div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleBack}>Back</button>
</div>
);
}
路由参数
动态片段
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
</Routes>
// Component
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams<{ userId: string }>();
return <div>User ID: {userId}</div>;
}
function Comment() {
const { postId, commentId } = useParams();
return (
<div>
Post: {postId}, Comment: {commentId}
</div>
);
}
可选片段
<Routes>
{/* Optional lang parameter */}
<Route path="/:lang?/products" element={<Products />} />
{/* Catch-all (splat) */}
<Route path="/files/*" element={<FileExplorer />} />
</Routes>
function FileExplorer() {
const { '*': filePath } = useParams();
// /files/documents/report.pdf → filePath = "documents/report.pdf"
return <div>Path: {filePath}</div>;
}
查询参数
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
const page = parseInt(searchParams.get('page') || '1');
const sort = searchParams.get('sort') || 'name';
const updateFilters = (newCategory: string) => {
setSearchParams({
category: newCategory,
page: '1', // Reset page
sort,
});
};
const nextPage = () => {
setSearchParams(prev => {
prev.set('page', String(page + 1));
return prev;
});
};
return (
<div>
<select
value={category}
onChange={(e) => updateFilters(e.target.value)}
>
<option value="all">All</option>
<option value="electronics">Electronics</option>
</select>
<ProductGrid category={category} page={page} sort={sort} />
<button onClick={nextPage}>Next Page</button>
</div>
);
}
嵌套路由
// Route configuration
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="analytics" element={<Analytics />} />
<Route path="settings" element={<Settings />} />
<Route path="users">
<Route index element={<UserList />} />
<Route path=":userId" element={<UserDetail />} />
<Route path="new" element={<NewUser />} />
</Route>
</Route>
</Routes>
// Parent layout with Outlet
import { Outlet, Link } from 'react-router-dom';
function DashboardLayout() {
return (
<div className="dashboard">
<nav className="sidebar">
<Link to="/dashboard">Home</Link>
<Link to="/dashboard/analytics">Analytics</Link>
<Link to="/dashboard/settings">Settings</Link>
<Link to="/dashboard/users">Users</Link>
</nav>
<main className="content">
{/* Child routes render here */}
<Outlet />
</main>
</div>
);
}
相对链接
function UserDetail() {
const { userId } = useParams();
return (
<div>
<h1>User {userId}</h1>
{/* Relative to current route */}
<Link to="edit">Edit User</Link> {/* → /dashboard/users/:userId/edit */}
<Link to="../">Back to List</Link> {/* → /dashboard/users */}
<Link to="../../">Dashboard</Link> {/* → /dashboard */}
</div>
);
}
受保护的路由
import { Navigate, Outlet, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }: { children?: ReactNode }) {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) {
return <LoadingSpinner />;
}
if (!user) {
// Redirect to login, preserving intended destination
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children ?? <Outlet />;
}
// Usage
<Routes>
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
// Role-based protection
function AdminRoute({ children }: { children?: ReactNode }) {
const { user } = useAuth();
if (user?.role !== 'admin') {
return <Navigate to="/unauthorized" replace />;
}
return children ?? <Outlet />;
}
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
{/* Admin only */}
<Route element={<AdminRoute />}>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/users/manage" element={<UserManagement />} />
</Route>
</Route>
</Routes>
最佳实践
- 使用嵌套路由共享布局
- 使用 loaders 获取数据(v6.4+)
- 实现适当的错误边界
- 在嵌套路由中使用相对链接
- 延迟加载路由组件
- 根据用户意图预加载(悬停时)
- 有可用的 loaders 时,不要在 useEffect 中获取数据
- 不要硬编码路径——使用相对导航
- 别忘了处理加载状态
何时不应使用此技能
- Next.js 应用——改用 Next.js App Router 或 Pages Router
- 服务端路由——使用框架专属的路由方案(如 Express)
- 简单的条件渲染——基本的显示/隐藏逻辑请使用
react技能 - 静态网站——考虑是否需要路由库
反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 有可用的 loaders 时在 useEffect 中获取数据 | 瀑布式加载,用户体验较差 | 使用 loader 函数 |
| 硬编码绝对路径 | 难以重构,会破坏嵌套路由 | 使用相对路径 |
| 未处理加载状态 | 用户体验较差 | 使用 useNavigation 或 Suspense |
| 缺少错误边界 | 路由出错时应用崩溃 | 在路由中添加 errorElement |
| 未延迟加载路由 | 初始包体积过大 | 使用 React.lazy() 加载路由 |
| 将 index 用作路由键 | 行为不正确 | 不要为路由使用键 |
| 忘记处理 404 | 空白页面或崩溃 | 添加 path="*" 的兜底路由 |
快速故障排除
| 问题 | 可能原因 | 修复方法 |
|---|---|---|
| 路由不匹配 | 路径语法错误 | 检查路径定义,使用精确路径 |
| 链接不起作用 | to 属性错误 | 确认路径以 / 开头,或使用相对路径 |
| 参数未定义 | 使用了错误的 Hook | 使用 useParams() 获取路由参数 |
| 导航不起作用 | 使用了错误的 Hook | 使用 useNavigate() 进行编程式导航 |
| 嵌套路由未显示 | 缺少 Outlet | 在父组件中添加 |
| Loader 未调用 | 使用了 Routes,而非 RouterProvider | 使用带有 loaders 的 createBrowserRouter |
| 受保护的路由不起作用 | 重定向逻辑错误 | 检查身份验证状态和 Navigate 组件 |
参考文档
- React Router
- 升级到 v6
- 高级模式
- MCP:
mcp__documentation__fetch_docs→ 技术:react,主题:router
评论
支持 Markdown