React从基础入门到高级实战:React 核心技术 - React Router:路由管理

React Router:路由管理

在现代 Web 应用开发中,路由管理 是构建多页面或单页应用(SPA)的核心技术之一。React Router 是 React 生态中最受欢迎的路由管理库,它为开发者提供了强大的工具来实现页面导航、动态路由和权限控制等功能。React Router v6 是其最新版本,相较于之前的版本,它带来了更简洁的 API、更灵活的嵌套路由支持以及更强大的导航工具,显著提升了开发体验。

本文专为希望实现多页面应用的开发者设计,内容涵盖 React Router v6 的安装与配置、核心路由组件、动态路由、导航方式、嵌套路由和路由守卫等核心主题。我们将通过一个简单的电商网站案例(包含首页、商品列表和详情页)以及一个练习任务(实现受保护的用户中心),帮助你深入掌握 React Router v6 的核心技能。


文章目标

  • 理解 React Router v6 的安装和基本配置。
  • 掌握核心路由组件(如 <Routes><Route>)的使用。
  • 实现动态路由和查询参数的处理。
  • 学会使用导航组件(<Link><NavLink>)和 Hook(useNavigate)。
  • 探索嵌套路由和路由守卫的实现方法。
  • 通过电商网站案例和练习任务巩固所学知识。

1. React Router v6 简介

1.1 什么是 React Router?

React Router 是一个专为 React 应用设计的路由管理库,它允许开发者根据 URL 动态渲染不同的组件,从而实现页面导航和切换。在单页应用(SPA)中,React Router 通过客户端路由管理页面的显示,无需刷新浏览器即可实现流畅的页面跳转。它是 React 生态中不可或缺的一部分,尤其适用于需要多页面结构的复杂应用。

1.2 为什么选择 React Router v6?

React Router v6 是 React Router 的最新版本,相较于 v5 和更早的版本,它引入了许多重要改进和新特性:

  • 更简洁的 API :移除了 <Switch> 组件,改为使用 <Routes><Route> 的组合,语法更直观。
  • 更强大的嵌套路由 :通过 <Outlet> 组件实现灵活的布局管理。
  • 动态路由增强:支持相对路径和更便捷的参数化路由。
  • 内置导航 Hook :提供了 useNavigateuseParams 等 Hook,简化路由操作。
  • 性能优化:路由匹配和渲染更加高效。

这些新特性使得 React Router v6 成为 2025 年及未来构建现代 Web 应用的首选工具,尤其是在 React 19 的新特性(如 Server Components)逐渐普及的背景下。


2. 安装与配置 React Router v6

2.1 安装

在 React 项目中安装 React Router v6,只需运行以下命令:

bash 复制代码
npm install react-router-dom@6

注意 :确保指定版本 @6,以避免安装旧版本或其他不兼容的版本。

2.2 基本配置

React Router v6 使用 <BrowserRouter> 作为路由容器,通常在应用的根组件中进行配置。<BrowserRouter> 利用 HTML5 History API 管理路由,支持干净的 URL(如 /about 而不是 #/about)。

代码示例

js 复制代码
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
  • <BrowserRouter>:最常用的路由容器,适合大多数 Web 应用。
  • 其他路由器选项
    • <HashRouter>:使用 URL 的 # 部分管理路由,适合静态文件部署。
    • <MemoryRouter>:将路由存储在内存中,适用于测试或服务器端渲染(SSR)场景。

App 组件中,我们将定义具体的路由规则。


3. 核心路由组件

React Router v6 的核心组件包括 <Routes><Route>,它们共同定义了应用的路由结构。

3.1 <Routes><Route>

  • <Routes>:路由容器的根组件,用于包裹所有 <Route>
  • <Route>:定义单个路由规则,通过 path 属性匹配 URL,通过 element 属性指定渲染的组件。

代码示例

js 复制代码
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}
  • path="/": 匹配根路径,渲染 Home 组件。
  • path="/about": 匹配 /about 路径,渲染 About 组件。
  • path="*": 通配符,匹配所有未定义的路径,渲染 NotFound 组件(404 页面)。

v6 新特性 :相比 v5 的 <Switch><Routes> 更加智能,能够自动匹配最佳路由,并支持嵌套路由的优化。

3.2 动态路由

动态路由允许根据 URL 中的参数渲染不同的内容。例如,/product/1/product/2 可以渲染同一个组件,但显示不同的商品详情。

代码示例

js 复制代码
// App.jsx
import { Route } from 'react-router-dom';
import ProductDetail from './pages/ProductDetail';

function App() {
  return (
    <Routes>
      <Route path="/product/:id" element={<ProductDetail />} />
    </Routes>
  );
}
  • :id:动态参数,表示 URL 中的变量部分。
  • 示例 URL:/product/1 会将 id 设置为 "1"

ProductDetail 组件中,使用 useParams Hook 获取参数:

js 复制代码
// pages/ProductDetail.jsx
import { useParams } from 'react-router-dom';

function ProductDetail() {
  const { id } = useParams();
  return <h1>商品 ID: {id}</h1>;
}
  • useParams:返回一个对象,包含所有动态参数。

3.3 查询参数

查询参数通过 URL 的 ?key=value 形式传递,常用于筛选、搜索等场景。React Router v6 提供了 useSearchParams Hook 来处理查询参数。

代码示例

js 复制代码
// pages/ProductList.jsx
import { useSearchParams } from 'react-router-dom';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category');

  return (
    <div>
      <h1>商品列表 - {category || '全部'}</h1>
      <button onClick={() => setSearchParams({ category: 'electronics' })}>
        筛选电子产品
      </button>
    </div>
  );
}
  • useSearchParams:返回一个数组,包含当前查询参数对象和更新函数。
  • searchParams.get('category'):获取 category 参数的值。
  • setSearchParams:更新 URL 的查询参数,例如点击按钮后 URL 变为 /products?category=electronics

导航是路由管理的核心功能之一,React Router v6 提供了多种方式来实现页面跳转。

4.1 <Link>

<Link> 组件用于创建导航链接,点击时跳转到指定路径,避免页面刷新。

代码示例

js 复制代码
import { Link } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <Link to="/">首页</Link> |{' '}
      <Link to="/about">关于我们</Link>
    </nav>
  );
}
  • to 属性:指定跳转的目标路径。
  • <Link> 渲染为 <a> 标签,但通过 React Router 内部机制实现客户端导航。

4.2 <NavLink>

<NavLink><Link> 的增强版,可以根据当前路径动态调整样式,例如高亮当前导航项。

代码示例

js 复制代码
import { NavLink } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <NavLink
        to="/"
        className={({ isActive }) => (isActive ? 'active' : '')}
      >
        首页
      </NavLink>{' '}
      |{' '}
      <NavLink
        to="/about"
        className={({ isActive }) => (isActive ? 'active' : '')}
      >
        关于我们
      </NavLink>
    </nav>
  );
}
  • className:接受一个函数,参数 isActive 表示当前路径是否匹配。
  • 示例 CSS:
css 复制代码
.active {
  color: red;
  font-weight: bold;
}

4.3 useNavigate Hook

useNavigate Hook 允许在组件内部以编程方式导航,适用于事件驱动的场景(如按钮点击、表单提交)。

代码示例

js 复制代码
import { useNavigate } from 'react-router-dom';

function Login() {
  const navigate = useNavigate();

  const handleLogin = () => {
    // 模拟登录逻辑
    navigate('/dashboard');
  };

  return <button onClick={handleLogin}>登录</button>;
}
  • navigate('/dashboard'):跳转到 /dashboard
  • navigate(-1):返回上一页,类似浏览器的"后退"按钮。
  • navigate('/path', { replace: true }):替换当前历史记录,而不是添加新记录。

v6 新特性useNavigate 替代了 v5 的 useHistory,API 更直观且功能更强大。


5. 嵌套路由与路由守卫

5.1 嵌套路由

嵌套路由允许在父路由中定义子路由,常用于实现包含公共布局的页面(如导航栏 + 主内容)。

代码示例

js 复制代码
// App.jsx
import { Routes, Route, Outlet } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="dashboard" element={<Dashboard />} />
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>
  );
}

// components/Layout.jsx
import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <nav>导航栏</nav>
      <main>
        <Outlet /> {/* 子路由渲染位置 */}
      </main>
    </div>
  );
}
  • <Route path="/" element={<Layout />}>:父路由,渲染 Layout 组件。
  • <Outlet />:子路由的占位符,指定子组件的渲染位置。
  • <Route index>:默认子路由,匹配父路径 / 时渲染。

v6 新特性<Outlet> 替代了 v5 中手动传递 children,嵌套路由更加简洁。

5.2 路由守卫

路由守卫用于保护某些路由,确保用户在访问受限页面前满足特定条件(如已登录)。React Router v6 没有内置的守卫组件,但可以通过自定义组件实现。

实现方式

js 复制代码
import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const isAuthenticated = false; // 模拟认证状态
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return children;
}

// 使用
<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>
  • ProtectedRoute:检查认证状态的自定义组件。
  • <Navigate>:未认证时重定向到 /login
  • state={``{ from: location }}:记录来源路径,登录后可返回。

6. 实践案例:电商网站

我们将通过一个简单的电商网站案例,综合应用以上知识。网站包含以下页面:

  • 首页:欢迎页面。
  • 商品列表:展示商品,支持筛选。
  • 商品详情:根据商品 ID 显示详情。

6.1 项目结构

复制代码
src/
├── pages/
│   ├── Home.jsx
│   ├── ProductList.jsx
│   ├── ProductDetail.jsx
├── components/
│   ├── Navbar.jsx
├── App.jsx
├── main.jsx

6.2 路由配置

js 复制代码
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';
import Navbar from './components/Navbar';

function App() {
  return (
    <div>
      <Navbar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<ProductList />} />
        <Route path="/product/:id" element={<ProductDetail />} />
      </Routes>
    </div>
  );
}

6.3 导航栏

js 复制代码
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>
        首页
      </NavLink>{' '}
      |{' '}
      <NavLink
        to="/products"
        className={({ isActive }) => (isActive ? 'active' : '')}
      >
        商品列表
      </NavLink>
    </nav>
  );
}

6.4 首页

js 复制代码
// pages/Home.jsx
function Home() {
  return <h1>欢迎来到电商网站</h1>;
}

6.5 商品列表

js 复制代码
// pages/ProductList.jsx
import { useSearchParams } from 'react-router-dom';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category');

  const products = [
    { id: 1, name: '手机', category: 'electronics' },
    { id: 2, name: '书籍', category: 'books' },
  ];

  const filteredProducts = category
    ? products.filter((p) => p.category === category)
    : products;

  return (
    <div>
      <h1>商品列表 - {category || '全部'}</h1>
      <button onClick={() => setSearchParams({ category: 'electronics' })}>
        电子产品
      </button>
      <button onClick={() => setSearchParams({ category: 'books' })}>
        书籍
      </button>
      <button onClick={() => setSearchParams({})}>全部</button>
      <ul>
        {filteredProducts.map((product) => (
          <li key={product.id}>
            <a href={`/product/${product.id}`}>{product.name}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

6.6 商品详情

js 复制代码
// pages/ProductDetail.jsx
import { useParams, useNavigate } from 'react-router-dom';

function ProductDetail() {
  const { id } = useParams();
  const navigate = useNavigate();

  const products = {
    1: { name: '手机', price: 2999 },
    2: { name: '书籍', price: 59 },
  };

  const product = products[id] || { name: '未知商品', price: 0 };

  return (
    <div>
      <h1>{product.name}</h1>
      <p>价格: ¥{product.price}</p>
      <button onClick={() => navigate('/products')}>返回列表</button>
    </div>
  );
}

这个案例展示了动态路由(/product/:id)、查询参数(筛选商品)和导航(<NavLink>useNavigate)的实际应用。


7. 练习:添加受保护的用户中心

现在,请尝试为电商网站添加一个受保护的 用户中心,包含以下要求:

  • 创建 /user 路由,渲染 UserCenter 组件。
  • 使用 ProtectedRoute 保护该路由,仅允许已登录用户访问。
  • UserCenter 中添加一个嵌套路由 /user/profile,渲染 Profile 组件。

参考实现

7.1 更新路由配置
js 复制代码
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';
import UserCenter from './pages/UserCenter';
import Profile from './pages/Profile';
import ProtectedRoute from './components/ProtectedRoute';
import Navbar from './components/Navbar';

function App() {
  return (
    <div>
      <Navbar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<ProductList />} />
        <Route path="/product/:id" element={<ProductDetail />} />
        <Route
          path="/user"
          element={
            <ProtectedRoute>
              <UserCenter />
            </ProtectedRoute>
          }
        >
          <Route path="profile" element={<Profile />} />
        </Route>
      </Routes>
    </div>
  );
}
7.2 实现 ProtectedRoute
js 复制代码
// components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const isAuthenticated = localStorage.getItem('token'); // 模拟认证
  return isAuthenticated ? children : <Navigate to="/login" replace />;
}
7.3 用户中心
js 复制代码
// pages/UserCenter.jsx
import { Link, Outlet } from 'react-router-dom';

function UserCenter() {
  return (
    <div>
      <h1>用户中心</h1>
      <nav>
        <Link to="/user/profile">编辑资料</Link>
      </nav>
      <Outlet /> {/* 渲染子路由 */}
    </div>
  );
}
7.4 个人资料页
jsx 复制代码
// pages/Profile.jsx
function Profile() {
  return <h2>个人资料页面</h2>;
}
7.5 更新导航栏
js 复制代码
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>
        首页
      </NavLink>{' '}
      |{' '}
      <NavLink
        to="/products"
        className={({ isActive }) => (isActive ? 'active' : '')}
      >
        商品列表
      </NavLink>{' '}
      |{' '}
      <NavLink
        to="/user"
        className={({ isActive }) => (isActive ? 'active' : '')}
      >
        用户中心
      </NavLink>
    </nav>
  );
}

测试步骤

  1. 未登录时(localStorage.getItem('token') 为 null),访问 /user 会跳转到 /login
  2. 模拟登录(localStorage.setItem('token', '123')),即可访问 /user/user/profile

8. 总结与进阶建议

React Router v6 是一个功能强大且灵活的路由管理工具,通过 <Routes><Route><Link> 等组件以及 useNavigateuseParams 等 Hook,开发者可以轻松实现多页面应用的导航和权限控制。本文通过电商网站案例和用户中心练习,展示了 React Router v6 的核心功能及其新特性(如 <Outlet> 和简化的 API)。

进阶建议

  • 数据加载器(Data Loaders):探索 React Router v6 的数据加载功能,提升页面加载性能。
  • 状态管理集成:结合 Redux 或 Context API,实现更复杂的路由逻辑。

希望这篇教程能为你的 React 开发之旅提供坚实的基础!如果有任何问题,欢迎交流。


以下是一个完整的 React Router v6 电商网站示例代码,供参考:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>电商网站 - React Router v6</title>
  <script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.development.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/react-router-dom@6/dist/umd/react-router-dom.development.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"></script>
  <style>
    nav { margin-bottom: 20px; }
    .active { color: red; font-weight: bold; }
    button { margin: 0 5px; }
  </style>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    const { BrowserRouter, Routes, Route, Link, NavLink, useParams, useNavigate, useSearchParams, Outlet, Navigate } = ReactRouterDOM;

    function ProtectedRoute({ children }) {
      const isAuthenticated = localStorage.getItem('token');
      return isAuthenticated ? children : <Navigate to="/login" replace />;
    }

    function Navbar() {
      return (
        <nav>
          <NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>首页</NavLink> |{' '}
          <NavLink to="/products" className={({ isActive }) => (isActive ? 'active' : '')}>商品列表</NavLink> |{' '}
          <NavLink to="/user" className={({ isActive }) => (isActive ? 'active' : '')}>用户中心</NavLink>
        </nav>
      );
    }

    function Home() {
      return <h1>欢迎来到电商网站</h1>;
    }

    function ProductList() {
      const [searchParams, setSearchParams] = useSearchParams();
      const category = searchParams.get('category');
      const products = [
        { id: 1, name: '手机', category: 'electronics' },
        { id: 2, name: '书籍', category: 'books' },
      ];
      const filteredProducts = category ? products.filter(p => p.category === category) : products;

      return (
        <div>
          <h1>商品列表 - {category || '全部'}</h1>
          <button onClick={() => setSearchParams({ category: 'electronics' })}>电子产品</button>
          <button onClick={() => setSearchParams({ category: 'books' })}>书籍</button>
          <button onClick={() => setSearchParams({})}>全部</button>
          <ul>
            {filteredProducts.map(product => (
              <li key={product.id}>
                <Link to={`/product/${product.id}`}>{product.name}</Link>
              </li>
            ))}
          </ul>
        </div>
      );
    }

    function ProductDetail() {
      const { id } = useParams();
      const navigate = useNavigate();
      const products = {
        1: { name: '手机', price: 2999 },
        2: { name: '书籍', price: 59 },
      };
      const product = products[id] || { name: '未知商品', price: 0 };

      return (
        <div>
          <h1>{product.name}</h1>
          <p>价格: ¥{product.price}</p>
          <button onClick={() => navigate('/products')}>返回列表</button>
        </div>
      );
    }

    function UserCenter() {
      return (
        <div>
          <h1>用户中心</h1>
          <nav>
            <Link to="/user/profile">编辑资料</Link>
          </nav>
          <Outlet />
        </div>
      );
    }

    function Profile() {
      return <h2>个人资料页面</h2>;
    }

    function Login() {
      const navigate = useNavigate();
      const handleLogin = () => {
        localStorage.setItem('token', '123');
        navigate('/user');
      };
      return <button onClick={handleLogin}>登录</button>;
    }

    function App() {
      return (
        <div>
          <Navbar />
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/products" element={<ProductList />} />
            <Route path="/product/:id" element={<ProductDetail />} />
            <Route path="/login" element={<Login />} />
            <Route path="/user" element={<ProtectedRoute><UserCenter /></ProtectedRoute>}>
              <Route path="profile" element={<Profile />} />
            </Route>
          </Routes>
        </div>
      );
    }

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

运行这段代码,你将得到一个完整的电商网站示例,包含所有功能点。祝你学习愉快!

相关推荐
EndingCoder13 分钟前
React从基础入门到高级实战:React 核心技术 - 表单处理与验证深度指南
前端·javascript·react.js·前端框架
EndingCoder15 分钟前
React从基础入门到高级实战:React 核心技术 - React 与 TypeScript:构建类型安全的应用
前端·安全·react.js·typescript·前端框架
谢尔登16 分钟前
【React】jsx 从声明式语法变成命令式语法
前端·react.js·前端框架
kooboo china.1 小时前
Tailwind css实战,基于Kooboo构建AI对话框页面(二)
前端·css
啃火龙果的兔子3 小时前
判断手机屏幕上的横向滑动(左滑和右滑)
javascript·react.js·智能手机
yuanmenglxb20044 小时前
react基础技术栈
前端·javascript·react.js
coding随想5 小时前
从SPDY到HTTP/2:网络协议的革新与未来
javascript
Magnum Lehar5 小时前
vulkan游戏引擎vulkan部分的fence实现
java·前端·游戏引擎
一枚码农4045 小时前
使用pnpm、vite搭建Phaserjs的开发环境
javascript·游戏·vite·phaserjs
FreeBuf_5 小时前
恶意npm与VS Code包窃取数据及加密货币资产
前端·npm·node.js