React Router 教程

项目概述

React Router 是 React 的官方路由库,提供了声明式的路由解决方案,用于构建单页应用(SPA)。它支持客户端路由、嵌套路由、参数路由、重定向、导航守卫等功能,使开发者能够轻松管理应用的导航状态和 URL 结构。

安装设置

1. 安装 React Router

# 安装核心包
npm install react-router-dom

# 或者使用 yarn
yarn add react-router-dom

2. 基本配置

在应用的根组件中设置路由配置:

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

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

核心功能

1. 基本路由

import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';

function Home() {
  return <h1>首页</h1>;
}

function About() {
  return <h1>关于我们</h1>;
}

function Contact() {
  return <h1>联系我们</h1>;
}

function App() {
  return (
    <div>
      {/* 导航链接 */}
      <nav>
        <Link to="/">首页</Link> |
        <Link to="/about">关于我们</Link> |
        <Link to="/contact">联系我们</Link>
      </nav>

      {/* 路由配置 */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </div>
  );
}

export default App;

2. 嵌套路由

import React from 'react';
import { Routes, Route, Link, Outlet } from 'react-router-dom';

function Dashboard() {
  return (
    <div>
      <h1>仪表盘</h1>
      <nav>
        <Link to="profile">个人资料</Link> |
        <Link to="settings">设置</Link> |
        <Link to="activity">活动记录</Link>
      </nav>
      {/* 子路由的渲染位置 */}
      <Outlet />
    </div>
  );
}

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

function Settings() {
  return <h2>设置</h2>;
}

function Activity() {
  return <h2>活动记录</h2>;
}

function App() {
  return (
    <div>
      <nav>
        <Link to="/">首页</Link> |
        <Link to="/dashboard">仪表盘</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />}>
          {/* 嵌套路由 */}
          <Route path="profile" element={<Profile />} />
          <Route path="settings" element={<Settings />} />
          <Route path="activity" element={<Activity />} />
        </Route>
      </Routes>
    </div>
  );
}

3. 参数路由

import React from 'react';
import { Routes, Route, Link, useParams } from 'react-router-dom';

function Users() {
  return (
    <div>
      <h1>用户列表</h1>
      <ul>
        <li><Link to="/users/1">用户 1</Link></li>
        <li><Link to="/users/2">用户 2</Link></li>
        <li><Link to="/users/3">用户 3</Link></li>
      </ul>
    </div>
  );
}

function UserDetail() {
  // 获取路由参数
  const { id } = useParams();
  return <h1>用户详情 - ID: {id}</h1>;
}

function App() {
  return (
    <div>
      <nav>
        <Link to="/">首页</Link> |
        <Link to="/users">用户列表</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/users" element={<Users />} />
        {/* 参数路由 */}
        <Route path="/users/:id" element={<UserDetail />} />
      </Routes>
    </div>
  );
}

4. 导航

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

// 基本链接
<Link to="/about">关于我们</Link>

// 带状态的链接
<Link to="/dashboard" state={{ from: 'home' }}>仪表盘</Link>

// 活跃状态的链接
<NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
  首页
</NavLink>

// 带样式的活跃链接
<NavLink 
  to="/about"
  style={({ isActive }) => ({ 
    color: isActive ? 'red' : 'blue' 
  })}
>
  关于我们
</NavLink>

使用编程式导航

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

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

  const handleLogin = () => {
    // 登录成功后导航
    navigate('/dashboard');
    
    // 带替换(不会在历史记录中添加新条目)
    navigate('/dashboard', { replace: true });
    
    // 带状态
    navigate('/dashboard', { 
      state: { token: 'abc123' },
      replace: true 
    });
    
    // 后退
    navigate(-1);
    
    // 前进
    navigate(1);
  };

  return (
    <div>
      <h1>登录</h1>
      <button onClick={handleLogin}>登录</button>
    </div>
  );
}

5. 重定向

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

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      {/* 重定向 */}
      <Route path="/old-path" element={<Navigate to="/new-path" replace />} />
      {/* 带状态的重定向 */}
      <Route 
        path="/login" 
        element={
          isLoggedIn ? (
            <Navigate to="/dashboard" state={{ from: '/login' }} />
          ) : (
            <LoginPage />
          )
        } 
      />
    </Routes>
  );
}

6. 404 页面

import { Routes, Route } from 'react-router-dom';

function NotFound() {
  return (
    <div>
      <h1>404</h1>
      <p>页面不存在</p>
      <Link to="/">返回首页</Link>
    </div>
  );
}

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

高级功能

1. 路由守卫

使用自定义组件实现路由守卫

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

function RequireAuth({ children }) {
  const location = useLocation();
  const isLoggedIn = checkIfUserIsLoggedIn(); // 自定义登录检查

  if (!isLoggedIn) {
    // 重定向到登录页,并保存当前位置以便登录后返回
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<LoginPage />} />
      {/* 受保护的路由 */}
      <Route 
        path="/dashboard" 
        element={
          <RequireAuth>
            <Dashboard />
          </RequireAuth>
        } 
      />
    </Routes>
  );
}

2. 路由加载器

使用 React Router v6.4+ 的加载器功能

import { createBrowserRouter, RouterProvider, useLoaderData } from 'react-router-dom';

// 加载器函数
async function userLoader({ params }) {
  const response = await fetch(`/api/users/${params.id}`);
  if (!response.ok) {
    throw new Error('Failed to load user');
  }
  return response.json();
}

// 使用加载器数据
function UserDetail() {
  const user = useLoaderData();
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: 'users/:id',
        element: <UserDetail />,
        loader: userLoader
      }
    ]
  }
]);

// 渲染路由
function Root() {
  return <RouterProvider router={router} />;
}

3. 路由操作器

使用 React Router v6.4+ 的操作器功能

import { createBrowserRouter, RouterProvider, useActionData, Form } from 'react-router-dom';

// 操作器函数
async function loginAction({ request }) {
  const formData = await request.formData();
  const credentials = {
    username: formData.get('username'),
    password: formData.get('password')
  };

  const response = await fetch('/api/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(credentials)
  });

  if (!response.ok) {
    return { error: '登录失败' };
  }

  // 登录成功,存储 token
  const data = await response.json();
  localStorage.setItem('token', data.token);
  return null;
}

// 登录页面
function LoginPage() {
  const actionData = useActionData();

  return (
    <div>
      <h1>登录</h1>
      {actionData?.error && <p>{actionData.error}</p>}
      <Form method="post">
        <input type="text" name="username" placeholder="用户名" />
        <input type="password" name="password" placeholder="密码" />
        <button type="submit">登录</button>
      </Form>
    </div>
  );
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: 'login',
        element: <LoginPage />,
        action: loginAction
      }
    ]
  }
]);

4. 嵌套布局

import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';

// 根布局
function RootLayout() {
  return (
    <div>
      <header>
        <h1>我的应用</h1>
      </header>
      <main>
        <Outlet />
      </main>
      <footer>
        <p>© 2024 我的应用</p>
      </footer>
    </div>
  );
}

// 仪表盘布局
function DashboardLayout() {
  return (
    <div>
      <nav>
        <Link to="profile">个人资料</Link>
        <Link to="settings">设置</Link>
      </nav>
      <Outlet />
    </div>
  );
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      {
        index: true,
        element: <Home />
      },
      {
        path: 'dashboard',
        element: <DashboardLayout />,
        children: [
          {
            index: true,
            element: <DashboardHome />
          },
          {
            path: 'profile',
            element: <Profile />
          },
          {
            path: 'settings',
            element: <Settings />
          }
        ]
      }
    ]
  }
]);

5. 路由状态管理

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

function CheckoutPage() {
  const location = useLocation();
  const navigate = useNavigate();

  // 获取之前页面传递的状态
  const cartItems = location.state?.cartItems || [];

  const handleCheckout = () => {
    // 处理结账逻辑
    // 成功后导航到确认页,并传递订单信息
    navigate('/confirmation', {
      state: {
        orderId: '12345',
        items: cartItems
      }
    });
  };

  return (
    <div>
      <h1>结账</h1>
      <div>商品数量: {cartItems.length}</div>
      <button onClick={handleCheckout}>确认结账</button>
    </div>
  );
}

function ConfirmationPage() {
  const location = useLocation();
  const orderId = location.state?.orderId;

  if (!orderId) {
    return <Navigate to="/checkout" replace />;
  }

  return (
    <div>
      <h1>订单确认</h1>
      <p>您的订单 {orderId} 已成功提交!</p>
    </div>
  );
}

实际应用场景

1. 认证系统

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

// 认证上下文
import { useAuth } from './AuthContext';

// 登录页面
function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();
  const { login } = useAuth();

  // 从 location.state 中获取重定向路径
  const from = location.state?.from?.pathname || '/';

  const handleSubmit = async (credentials) => {
    const success = await login(credentials);
    if (success) {
      // 登录成功后重定向到之前的页面
      navigate(from, { replace: true });
    }
  };

  return (
    <div>
      <h1>登录</h1>
      <LoginForm onSubmit={handleSubmit} />
    </div>
  );
}

// 注册页面
function RegisterPage() {
  const navigate = useNavigate();
  const { register } = useAuth();

  const handleSubmit = async (userData) => {
    const success = await register(userData);
    if (success) {
      navigate('/dashboard');
    }
  };

  return (
    <div>
      <h1>注册</h1>
      <RegisterForm onSubmit={handleSubmit} />
    </div>
  );
}

// 认证路由守卫
function ProtectedRoute({ children }) {
  const { isAuthenticated } = useAuth();
  const location = useLocation();

  if (!isAuthenticated) {
    // 未认证时重定向到登录页
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

// 应用路由
function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<LoginPage />} />
      <Route path="/register" element={<RegisterPage />} />
      <Route 
        path="/dashboard" 
        element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        } 
      />
      <Route 
        path="/profile" 
        element={
          <ProtectedRoute>
            <Profile /> 
          </ProtectedRoute>
        } 
      />
    </Routes>
  );
}

2. 电商应用

import { Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';

// 商品列表
function ProductList() {
  const navigate = useNavigate();

  const handleProductClick = (productId) => {
    navigate(`/products/${productId}`);
  };

  return (
    <div>
      <h1>商品列表</h1>
      <div className="product-grid">
        {products.map(product => (
          <div 
            key={product.id} 
            className="product-card"
            onClick={() => handleProductClick(product.id)}
          >
            <h3>{product.name}</h3>
            <p>${product.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

// 商品详情
function ProductDetail() {
  const { id } = useParams();
  const navigate = useNavigate();
  const product = products.find(p => p.id === id);

  if (!product) {
    return <div>商品不存在</div>;
  }

  const handleAddToCart = () => {
    addToCart(product);
    navigate('/cart');
  };

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
      <button onClick={handleAddToCart}>加入购物车</button>
      <Link to="/products">返回商品列表</Link>
    </div>
  );
}

// 购物车
function Cart() {
  const navigate = useNavigate();
  const cartItems = getCartItems();

  const handleCheckout = () => {
    navigate('/checkout');
  };

  return (
    <div>
      <h1>购物车</h1>
      {cartItems.length === 0 ? (
        <p>购物车为空</p>
      ) : (
        <>
          {cartItems.map(item => (
            <div key={item.id}>
              <h3>{item.name}</h3>
              <p>${item.price} × {item.quantity}</p>
            </div>
          ))}
          <div>总计: ${calculateTotal()}</div>
          <button onClick={handleCheckout}>去结账</button>
        </>
      )}
      <Link to="/products">继续购物</Link>
    </div>
  );
}

// 结账
function Checkout() {
  const navigate = useNavigate();

  const handleSubmitOrder = () => {
    // 处理订单提交
    navigate('/confirmation');
  };

  return (
    <div>
      <h1>结账</h1>
      <CheckoutForm onSubmit={handleSubmitOrder} />
    </div>
  );
}

// 订单确认
function Confirmation() {
  return (
    <div>
      <h1>订单确认</h1>
      <p>您的订单已成功提交!</p>
      <Link to="/products">继续购物</Link>
    </div>
  );
}

// 应用路由
function App() {
  return (
    <div>
      <nav>
        <Link to="/">首页</Link> |
        <Link to="/products">商品</Link> |
        <Link to="/cart">购物车</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<ProductList />} />
        <Route path="/products/:id" element={<ProductDetail />} />
        <Route path="/cart" element={<Cart />} />
        <Route path="/checkout" element={<Checkout />} />
        <Route path="/confirmation" element={<Confirmation />} />
      </Routes>
    </div>
  );
}

3. 管理后台

import { createBrowserRouter, RouterProvider, Outlet, NavLink } from 'react-router-dom';

// 后台布局
function AdminLayout() {
  return (
    <div className="admin-panel">
      <aside>
        <h2>管理后台</h2>
        <nav>
          <NavLink to="dashboard">仪表盘</NavLink>
          <NavLink to="users">用户管理</NavLink>
          <NavLink to="products">商品管理</NavLink>
          <NavLink to="orders">订单管理</NavLink>
        </nav>
      </aside>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

// 仪表盘
function AdminDashboard() {
  return (
    <div>
      <h1>仪表盘</h1>
      <div className="stats">
        <div>总用户: 1000</div>
        <div>总商品: 500</div>
        <div>总订单: 2000</div>
      </div>
    </div>
  );
}

// 用户管理
function UserManagement() {
  return (
    <div>
      <h1>用户管理</h1>
      {/* 用户列表和管理功能 */}
    </div>
  );
}

// 商品管理
function ProductManagement() {
  return (
    <div>
      <h1>商品管理</h1>
      {/* 商品列表和管理功能 */}
    </div>
  );
}

// 订单管理
function OrderManagement() {
  return (
    <div>
      <h1>订单管理</h1>
      {/* 订单列表和管理功能 */}
    </div>
  );
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: 'admin',
        element: <AdminLayout />,
        children: [
          {
            index: true,
            element: <AdminDashboard />
          },
          {
            path: 'users',
            element: <UserManagement />
          },
          {
            path: 'products',
            element: <ProductManagement />
          },
          {
            path: 'orders',
            element: <OrderManagement />
          }
        ]
      }
    ]
  }
]);

// 渲染应用
function Root() {
  return <RouterProvider router={router} />;
}

代码优化建议

  1. 路由配置分离:将路由配置分离到单独的文件中,提高代码可维护性
  2. 使用懒加载:对于大型应用,使用 React.lazy() 和 Suspense 实现路由懒加载
  3. 路由守卫抽象:将认证逻辑抽象为可重用的路由守卫组件
  4. 嵌套路由合理使用:利用嵌套路由和布局组件减少代码重复
  5. 参数验证:对路由参数进行验证,确保数据安全性
  6. 错误边界:为路由组件添加错误边界,提高应用稳定性
  7. 导航状态管理:合理使用 location.state 传递临时数据
  8. SEO 考虑:对于需要 SEO 的页面,考虑使用服务端渲染或静态生成
  9. 性能优化
    • 避免在路由组件中进行昂贵的计算
    • 使用 memo 缓存路由组件
    • 合理使用 useMemo 和 useCallback
  10. 代码组织
    • 按功能模块组织路由和组件
    • 使用一致的命名规范
    • 添加适当的注释

参考资源

« 上一篇 Sequelize 教程 - 基于 Promise 的 Node.js ORM 下一篇 » Vue Router 教程 - Vue.js 的官方路由