React Router 路由:单页应用导航与守卫

FreeGuideOnline 最新 2026-06-15

React Router 路由:单页应用导航与守卫

在现代 Web 开发中,单页应用(SPA)已成为主流。React Router 是 React 生态中最流行的路由库,它让你无需刷新页面即可在不同视图间平滑切换,同时支持动态参数、嵌套布局和路由守卫。本教程从零开始,带你掌握 React Router v6 的核心用法,并实现受保护的路由。

为什么需要 React Router

传统的多页应用每次跳转都会向服务器请求全新页面,导致白屏和状态丢失。React Router 通过客户端路由,只替换页面中变化的组件,保持应用状态,带来近似原生 App 的流畅体验。它基于浏览器 History API,URL 变化时仅重新渲染匹配的组件树。

安装与快速上手

首先,通过 npm 或 yarn 安装 React Router v6:

npm install react-router-dom
# 或
yarn add react-router-dom

在你的应用入口文件中,引入并配置 BrowserRouter,它作为整个路由系统的容器:

import { BrowserRouter } from 'react-router-dom';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

接下来,在 App 组件中使用 RoutesRoute 定义页面映射:

import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

<Routes> 会遍历其子 <Route>,仅渲染第一个匹配的 element。没有匹配时,可以添加一个 * 通配符路由作为 404 页面。

声明式导航与链接

应用内跳转绝不能用 <a> 标签,因为它会触发完整页面刷新。React Router 提供三种内置导航方式:

生成标准的无障碍链接,点击时拦截默认行为,执行客户端路由:

import { Link } from 'react-router-dom';

<Link to="/">首页</Link>
<Link to="/about">关于</Link>

to 属性可以是字符串,也可以是对象,用于携带查询参数或 hash:

<Link to={{ pathname: '/products', search: '?category=books' }}>书籍</Link>

NavLink 继承自 Link,但多了一个激活态样式。当 to 与当前 URL 匹配时,会自动添加 active 类名,也可以自定义样式或类名:

<NavLink 
  to="/dashboard" 
  className={({ isActive }) => isActive ? 'text-blue-600 font-bold' : 'text-gray-600'}
>
  仪表盘
</NavLink>

end 属性可禁用祖先路由的匹配,避免嵌套路由时父链接持续高亮。

3. 编程式导航:useNavigate

在事件处理函数或副作用中跳转,使用 useNavigate 钩子:

import { useNavigate } from 'react-router-dom';

function LoginButton() {
  const navigate = useNavigate();
  const handleLogin = () => {
    // 登录逻辑...
    navigate('/dashboard', { replace: true });
  };
  return <button onClick={handleLogin}>登录</button>;
}

navigate 还支持传递 state 数据,并通过目标组件的 useLocation 获取:

navigate('/product/123', { state: { fromCatalog: true } });

动态路由与 URL 参数

许多页面需要根据 ID、slug 等参数改变内容。React Router 使用 :paramName 占位符定义动态路径:

<Route path="/users/:userId" element={<UserProfile />} />

UserProfile 组件内,通过 useParams 提取参数:

import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  // 使用 userId 获取数据...
  return <div>用户 ID{userId}</div>;
}

多个参数或无限制子级也可支持:

<Route path="/files/*" element={<FileViewer />} />
// useParams 返回 { "*": "path/to/file" }

嵌套路由与布局

嵌套路由让页面共享布局(如侧边栏、顶栏),避免重复代码。在父组件中使用 <Outlet /> 作为子路由的渲染出口。

定义嵌套路由:

<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardHome />} />
  <Route path="analytics" element={<Analytics />} />
  <Route path="settings" element={<Settings />} />
</Route>

DashboardLayout 组件:

import { Outlet } from 'react-router-dom';
import Sidebar from './Sidebar';

function DashboardLayout() {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">
        <Outlet /> {/* 子路由内容在此显示 */}
      </main>
    </div>
  );
}

index 路由匹配父路由的默认子页面(即 /dashboard 精确路径)。

路由守卫:保护私密页面

路由守卫控制用户的访问权限,例如未登录用户跳转到登录页。React Router v6 没有内置的守卫组件,但可以通过封装一个 ProtectedRoute 高阶组件轻松实现。

常见模式:

import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const isAuthenticated = useAuth(); // 你的认证钩子,返回 true/false
  const location = useLocation();

  if (!isAuthenticated) {
    // 将用户重定向到登录页,并记录来源路径以便登陆后返回
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

在路由配置中使用:

<Route path="/admin" element={
  <ProtectedRoute>
    <AdminPanel />
  </ProtectedRoute>
} />

如果需要更复杂的权限(如角色检查),可以在 ProtectedRoute 中扩展逻辑,返回不同内容或 <Navigate />

基于角色的守卫示例

function RoleGuard({ allowedRoles, children }) {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" />;
  if (!allowedRoles.includes(user.role)) return <Navigate to="/unauthorized" />;
  return children;
}

<Route path="/admin" element={
  <RoleGuard allowedRoles={['admin', 'editor']}>
    <AdminPanel />
  </RoleGuard>
} />

代码分割与路由懒加载

大型应用中,按路由拆分代码可以显著提升首屏加载速度。React 的 lazySuspense 配合路由组件即可:

import { lazy, Suspense } from 'react';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

这样,每个页面仅在首次访问时加载对应的 JS 包。

处理查询参数与 state

除路径参数外,常需要获取查询字符串 ?key=value 或跨页面传递状态。

读取查询参数:useSearchParams

import { useSearchParams } from 'react-router-dom';

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const keyword = searchParams.get('q') || '';

  const updateKeyword = (newValue) => {
    setSearchParams({ q: newValue });
  };

  // ...
}

useSearchParams 类似 useState,直接操作即可同步 URL。

路由状态传递

通过 navigateLinkstate 属性传递不显示在 URL 中的数据:

// 发送端
navigate('/product', { state: { category: 'electronics' } });

// 接收端
import { useLocation } from 'react-router-dom';
const location = useLocation();
const category = location.state?.category;

注意:state 在浏览器刷新后默认保留(基于 history.state),但若用户手动清空浏览器历史,则会丢失。

总结

React Router v6 用声明式组件和钩子,让单页应用的路由管理清晰而强大。掌握基础路由配置、动态参数、嵌套布局和守卫后,你已能构建任意复杂度的导航逻辑。关键实践:

  • 始终使用 LinkuseNavigate,避免硬刷新。
  • 利用 Outlet 实现布局复用。
  • ProtectedRoute + Navigate 实现认证守卫。
  • 懒加载路由以优化性能。

现在,你可以开始在项目中构建流畅、安全的导航体验了。