mcpField
R
react-router

react-router

github.com/claude-dev-suite开发4637 次安装3 天前更新
代码仓库

关于 react-router

面向 React 开发者的 React Router 构建参考技能,涵盖导航、路由参数、嵌套路由、数据加载及常见路由问题。

主要功能

  1. 01链接与编程式导航
  2. 02路由参数与查询参数
  3. 03嵌套与受保护路由
  4. 04Loader 与错误边界
  5. 05路由实践与故障排查

适用场景

  1. 01配置 React 应用中的导航
  2. 02构建共享布局的嵌套页面
  3. 03通过路由 Loader 加载数据
  4. 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 组件

参考文档

评论

支持 Markdown