React 路由配置

React Router是React生态系统中最流行的路由库,用于创建单页应用(SPA)的导航和路由系统。

React Router简介

React Router是一个声明式的路由库,它允许你在React应用中定义不同的"页面",并在它们之间导航而无需重新加载页面。

安装

安装React Router包

配置

定义路由和组件映射

导航

使用Link组件导航

渲染

根据URL渲染对应组件

安装React Router

# React Router v6(当前最新版本)
npm install react-router-dom@6

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

# 安装类型定义(如果使用TypeScript)
npm install --save-dev @types/react-router-dom

React Router v6

  • 最新版本,推荐新项目使用
  • 更简单的API设计
  • 支持嵌套路由
  • 相对路径支持更好
  • 路由守卫更灵活
  • 使用Routes替代Switch

React Router v5

  • 旧版本,老项目可能还在使用
  • 使用Switch组件
  • 路由守卫通过高阶组件实现
  • 嵌套路由配置不同
  • 相对路径处理较复杂

基本路由配置

// App.js - 主应用组件
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

// 页面组件
function Home() {
  return <h2>首页</h2>;
}

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

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

function NotFound() {
  return <h2>404 - 页面未找到</h2>;
}

function App() {
  return (
    <BrowserRouter>
      <div>
        <nav>
          <ul style={{ display: 'flex', gap: '20px', listStyle: 'none' }}>
            <li>
              <Link to="/">首页</Link>
            </li>
            <li>
              <Link to="/about">关于</Link>
            </li>
            <li>
              <Link to="/contact">联系</Link>
            </li>
          </ul>
        </nav>

        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;

嵌套路由

/dashboard
/dashboard/profile
/dashboard/settings
// Dashboard.js - 父级路由组件
import React from 'react';
import { Routes, Route, Link, Outlet } from 'react-router-dom';

function Dashboard() {
  return (
    <div>
      <h2>仪表板</h2>
      <nav>
        <Link to="profile">个人资料</Link>
        <Link to="settings">设置</Link>
      </nav>

      {/* Outlet用于渲染子路由 */}
      <Outlet />
    </div>
  );
}

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

function Settings() {
  return <h3>设置页面</h3>;
}

// App.js中的路由配置
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} >
          {/* 嵌套路由 */}
          <Route path="profile" element={<Profile />} />
          <Route path="settings" element={<Settings />} />
          {/* 默认子路由 */}
          <Route index element={<DashboardHome />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
路由树结构示例:
/
/about
/dashboard
/dashboard/profile
/dashboard/settings
/dashboard/analytics
/contact

动态路由与参数

// 产品详情页面
function ProductDetail() {
  // 使用useParams获取路由参数
  const { productId } = useParams();

  return (
    <div>
      <h2>产品详情</h2>
      <p>产品ID: {productId}</p>
    </div>
  );
}

// 用户个人资料页面
function UserProfile() {
  const { username } = useParams();
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 根据username参数获取用户数据
    fetchUser(username).then(setUser);
  }, [username]);

  return (
    <div>
      {user ? (
        <div>
          <h2>{user.name}的个人资料</h2>
          <p>邮箱: {user.email}</p>
        </div>
      ) : (
        <p>加载中...</p>
      )}
    </div>
  );
}

// 路由配置
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products/:productId" element={<ProductDetail />} />
        <Route path="/user/:username" element={<UserProfile />} />
        {/* 可选参数 */}
        <Route path="/posts/:postId?" element={<Post />} />
        {/* 多个参数 */}
        <Route path="/category/:categoryId/product/:productId" element={<Product />} />
      </Routes>
    </BrowserRouter>
  );
}

导航组件

Link组件

用于声明式导航

<Link to="/home">首页</Link>
<Link to="/about" state={{ from: 'header' }}>关于</Link>
<Link to="../parent">返回上级</Link>
NavLink组件

带活动状态的链接

<NavLink
  to="/messages"
  className={({ isActive }) =>
    isActive ? 'active-link' : 'normal-link'
  }
>
  消息
</NavLink>
编程式导航

使用useNavigate

const navigate = useNavigate();

const handleClick = () => {
  navigate('/home');
  navigate(-1); // 后退
  navigate('/user', { state: { id: 123 } });
};

路由守卫与权限控制

// 认证上下文
const AuthContext = React.createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// 受保护的路由组件
function ProtectedRoute({ children }) {
  const { user } = useContext(AuthContext);
  const location = useLocation();

  if (!user) {
    // 重定向到登录页,并保存当前路径
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

// 角色权限路由
function RoleProtectedRoute({ children, allowedRoles }) {
  const { user } = useContext(AuthContext);
  const location = useLocation();

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

  if (!allowedRoles.includes(user.role)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return children;
}

// 路由配置
function App() {
  return (
    <AuthProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />

          <Route path="/dashboard" element={
            <ProtectedRoute>
              <Dashboard />
            </ProtectedRoute>
          } />

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

          <Route path="/unauthorized" element={<Unauthorized />} />
        </Routes>
      </BrowserRouter>
    </AuthProvider>
  );
}

路由演示区

首页

欢迎来到首页!请点击上面的按钮切换路由。

懒加载与代码分割

import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// 使用lazy()进行组件懒加载
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

// 加载中组件
function LoadingSpinner() {
  return (
    <div style={{
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      height: '100vh'
    }}>
      <div className="spinner-border" role="status">
        <span className="visually-hidden">加载中...</span>
      </div>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="/dashboard/*" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// 预加载组件(优化体验)
const preloadComponent = (importFunction) => {
  const component = importFunction();
  return component;
};

// 在用户可能访问前预加载
const preloadDashboard = () => preloadComponent(() => import('./pages/Dashboard'));

// 在鼠标悬停时预加载
<Link
  to="/dashboard"
  onMouseEnter={preloadDashboard}
>
  仪表板
</Link>

路由Hook

Hook 用途 示例
useParams 获取路由参数 const { id } = useParams();
useNavigate 编程式导航 const navigate = useNavigate();
useLocation 获取当前位置对象 const location = useLocation();
useSearchParams 获取和设置查询参数 const [search, setSearch] = useSearchParams();
useRoutes 声明式路由配置 const routes = useRoutes(routeConfig);
useOutlet 获取嵌套路由的Outlet const outlet = useOutlet();
useOutletContext 访问Outlet传递的上下文 const context = useOutletContext();
useResolvedPath 解析相对路径 const path = useResolvedPath('../parent');

查询参数处理

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

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const location = useLocation();

  // 获取查询参数
  const category = searchParams.get('category');
  const sort = searchParams.get('sort') || 'newest';
  const page = parseInt(searchParams.get('page')) || 1;

  // 更新查询参数
  const handleFilterChange = (newCategory) => {
    setSearchParams({
      category: newCategory,
      page: 1, // 重置页码
      sort
    });
  };

  const handlePageChange = (newPage) => {
    setSearchParams({
      category,
      page: newPage,
      sort
    });
  };

  // 构建查询字符串
  const queryString = location.search;

  return (
    <div>
      <h2>产品列表</h2>
      <div>
        <button onClick={() => handleFilterChange('electronics')}>
          电子产品
        </button>
        <button onClick={() => handleFilterChange('clothing')}>
          服装
        </button>
      </div>
      <p>当前分类: {category || '全部'}</p>
      <p>当前页码: {page}</p>
      <button onClick={() => handlePageChange(page + 1)}>
        下一页
      </button>
    </div>
  );
}

路由配置模式

基础路由
集中式配置
const routes = [
  { path: '/', element: <Home /> },
  { path: '/about', element: <About /> },
  { path: '/contact', element: <Contact /> },
];
嵌套路由
模块化配置
// routes/index.js
export const routes = [
  { path: '/', element: <Home /> },
  { path: '/dashboard/*', element: <DashboardLayout /> },
];

// routes/dashboard.js
export const dashboardRoutes = [
  { path: 'profile', element: <Profile /> },
  { path: 'settings', element: <Settings /> },
];
动态路由
基于数据生成路由
const userRoutes = users.map(user => ({
  path: `/user/${user.id}`,
  element: <UserProfile user={user} />,
}));

路由动画与过渡效果

import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';

function AnimatedRoutes() {
  const location = useLocation();

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/" element={
          <motion.div
            initial={{ opacity: 0, x: -100 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: 100 }}
            transition={{ duration: 0.5 }}
          >
            <Home />
          </motion.div>
        } />

        <Route path="/about" element={
          <motion.div
            initial={{ opacity: 0, scale: 0.8 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0.8 }}
            transition={{ duration: 0.3 }}
          >
            <About />
          </motion.div>
        } />
      </Routes>
    </AnimatePresence>
  );
}

// 使用CSS过渡
function CSSAnimatedRoutes() {
  const location = useLocation();

  return (
    <div className="router-transition">
      <Routes location={location}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </div>
  );
}

// CSS样式
.router-transition {
  transition: opacity 0.3s ease;
}

.router-transition-enter {
  opacity: 0;
}

.router-transition-enter-active {
  opacity: 1;
}

最佳实践

  1. 使用命名导出:组件使用命名导出,方便懒加载
  2. 路由分割:按功能模块分割路由配置
  3. 错误边界:为路由组件添加错误边界
  4. 保持URL简洁:避免过长的URL参数
  5. SEO友好:为静态页面提供有意义的URL
  6. 性能优化:使用懒加载和预加载
  7. 类型安全:使用TypeScript定义路由参数类型
  8. 测试覆盖:测试路由导航和参数传递
常见问题与解决方案:
  • 页面刷新404:配置服务器支持SPA(使用HashRouter或服务器配置)
  • 路由参数类型错误:使用TypeScript或运行时验证
  • 嵌套路由不显示:确保父路由有<Outlet />
  • 导航循环:检查路由守卫逻辑,避免无限重定向
  • 路由跳转后滚动位置:使用useScrollRestoration
React Router交互控制台
输入 'help' 查看可用命令

当前路由信息