# React Router v6

# 简介

React Router 是 React 生态系统中最流行的路由库,用于构建单页应用(SPA)。React Router v6 是一次重大升级,它在保持 React Router 基本思想的同时,引入了许多新概念和简化的 API。

React Router v6 的主要目标:

  • 减小包体积
  • 简化 API
  • 提高灵活性
  • 实现更好的嵌套路由
  • 提供改进的类型定义
  • 更好地与 React 的最新特性集成

# 安装

# 使用 npm
npm install react-router-dom

# 使用 yarn
yarn add react-router-dom

# 使用 pnpm
pnpm add react-router-dom

# 基础组件和概念

React Router v6 中的基础组件:

# BrowserRouter

为应用提供路由功能,通常在应用的根组件中使用:

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

function App() {
  return (
    <BrowserRouter>
      {/* 应用的其余部分 */}
    </BrowserRouter>
  );
}

# Routes 和 Route

Routes 组件包裹所有的 Route 组件,用于定义路由匹配规则:

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

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

用于导航的组件:

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

function Navigation() {
  return (
    <nav>
      <Link to="/">首页</Link>
      <NavLink 
        to="/about"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        关于我们
      </NavLink>
    </nav>
  );
}

NavLinkLink 多了一些属性,例如可以根据当前路由是否匹配来添加样式。

# React Router v6 的新特性

# 1. 嵌套路由和 Outlet

v6 中的嵌套路由变得更加直观和强大:

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="products" element={<Products />}>
            <Route index element={<FeaturedProducts />} />
            <Route path=":id" element={<ProductDetail />} />
          </Route>
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Layout() {
  return (
    <div>
      <header>Header</header>
      <main>
        {/* 子路由组件将在这里渲染 */}
        <Outlet />
      </main>
      <footer>Footer</footer>
    </div>
  );
}

Outlet 组件指定子路由组件的渲染位置,类似于"插槽"的概念。

# 2. index 路由

index 路由用于指定父路由路径的默认子路由:

<Route path="products" element={<Products />}>
  <Route index element={<FeaturedProducts />} />
  <Route path=":id" element={<ProductDetail />} />
</Route>

当访问 /products 时,会渲染 ProductsFeaturedProducts

# 3. 动态参数获取

使用 useParams hook 获取 URL 参数:

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

function ProductDetail() {
  const { id } = useParams();
  
  return <div>产品详情:{id}</div>;
}

# 4. 导航和重定向

# useNavigate Hook

替代了 v5 中的 useHistory

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

function ProductItem({ product }) {
  const navigate = useNavigate();
  
  return (
    <div onClick={() => navigate(`/products/${product.id}`)}>
      {product.name}
    </div>
  );
}

用于声明式重定向:

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

function ProtectedRoute({ children, isAuthenticated }) {
  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }
  
  return children;
}

# 5. 处理 location 和查询参数

# useLocation Hook

获取当前 location 对象:

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

function App() {
  const location = useLocation();
  
  return (
    <div>
      <p>当前路径: {location.pathname}</p>
      <p>搜索参数: {location.search}</p>
    </div>
  );
}

# useSearchParams Hook

更便捷地处理查询参数:

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

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category') || 'all';
  
  return (
    <div>
      <h1>产品列表 - 分类: {category}</h1>
      <button onClick={() => setSearchParams({ category: 'electronics' })}>
        电子产品
      </button>
      <button onClick={() => setSearchParams({ category: 'clothing' })}>
        服装
      </button>
    </div>
  );
}

# 6. 懒加载路由

结合 React.lazy 和 Suspense 实现路由懒加载:

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

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

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/products/*" element={<Products />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

# 实用的路由模式

# 1. 数据加载

使用 loader 特性(React Router v6.4+)预加载组件数据:

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

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
    children: [
      {
        path: 'products',
        element: <Products />,
        loader: async () => {
          return fetch('/api/products');
        },
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

在组件中使用 useLoaderData 获取预加载的数据:

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

function Products() {
  const products = useLoaderData();
  
  return (
    <div>
      <h1>产品列表</h1>
      <ul>
        {products.map(product => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

# 2. 数据提交

使用 action 特性处理表单提交(React Router v6.4+):

import { Form, redirect } from 'react-router-dom';

// 路由配置
const routes = [
  {
    path: '/products/new',
    element: <NewProduct />,
    action: async ({ request }) => {
      const formData = await request.formData();
      const productData = Object.fromEntries(formData);
      
      await createProduct(productData);
      return redirect('/products');
    },
  },
];

// 组件
function NewProduct() {
  return (
    <div>
      <h1>添加新产品</h1>
      <Form method="post">
        <label>
          名称: <input name="name" />
        </label>
        <label>
          价格: <input name="price" type="number" />
        </label>
        <button type="submit">保存</button>
      </Form>
    </div>
  );
}

# 3. 受保护的路由

实现需要认证的路由:

import { useAuth } from './auth-context';

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

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/login" element={<Login />} />
      <Route 
        path="/dashboard" 
        element={
          <ProtectedRoute>
            <Dashboard />
          </ProtectedRoute>
        } 
      />
    </Routes>
  );
}

# React Router v6 vs v5 的主要区别

功能 React Router v5 React Router v6
路由匹配 <Switch> <Routes>
默认匹配行为 部分匹配 精确匹配
嵌套路由 需要完整路径 相对路径 + <Outlet>
重定向 <Redirect> <Navigate>
历史导航 useHistory() useNavigate()
路由配置 不支持集中式配置 支持集中式配置
路由元数据 不支持 支持
相对链接 不完全支持 完全支持
404 路由 <Route path="*"> <Route path="*">

# 迁移指南

从 React Router v5 迁移到 v6 的关键步骤:

  1. <Switch> 替换为 <Routes>
  2. 移除 exact 属性(v6 默认精确匹配)
  3. componentrender 属性替换为 element
  4. 调整嵌套路由的结构,使用 <Outlet>
  5. useNavigate 替代 useHistory
  6. <Redirect> 替换为 <Navigate>
  7. 更新自定义钩子和组件以适应新 API
// v5
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/about" render={() => <About />} />
  <Redirect from="/old-path" to="/new-path" />
  <Route path="*" component={NotFound} />
</Switch>

// v6
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="/old-path" element={<Navigate to="/new-path" replace />} />
  <Route path="*" element={<NotFound />} />
</Routes>

# 性能优化

# 1. 使用 createBrowserRouter

React Router v6.4+ 推荐使用 createBrowserRouter 进行路由配置,它提供更好的性能和类型安全:

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

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      { path: 'about', element: <About /> },
      {
        path: 'products',
        element: <Products />,
        children: [
          { index: true, element: <ProductList /> },
          { path: ':id', element: <ProductDetail /> },
        ],
      },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

# 2. 懒加载与代码分割

结合 React.lazy 和路由系统,实现高效的代码分割:

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

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

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      { index: true, element: <Suspense fallback={<div>Loading...</div>}><Home /></Suspense> },
      { path: 'about', element: <Suspense fallback={<div>Loading...</div>}><About /></Suspense> },
      { path: 'products/*', element: <Suspense fallback={<div>Loading...</div>}><Products /></Suspense> },
    ],
  },
]);

# 3. 预加载数据

使用 loader 特性预加载数据,避免渲染后再获取数据导致的页面闪烁:

const router = createBrowserRouter([
  {
    path: 'dashboard',
    element: <Dashboard />,
    loader: async () => {
      const [userData, statsData] = await Promise.all([
        fetch('/api/user').then(r => r.json()),
        fetch('/api/stats').then(r => r.json())
      ]);
      
      return { user: userData, stats: statsData };
    },
  },
]);

# 常见问题与解决方案

# 1. 如何在导航时传递状态?

// 传递状态
<Link to="/product/1" state={{ from: 'search', query: 'electronics' }}>
  产品 1
</Link>

// 或通过编程方式
navigate('/product/1', { state: { from: 'search', query: 'electronics' } });

// 在目标组件中获取状态
const location = useLocation();
const { from, query } = location.state || {};

# 2. 如何处理路由鉴权?

function RequireAuth({ children }) {
  let auth = useAuth();
  let location = useLocation();

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

  return children;
}

<Routes>
  <Route path="/login" element={<Login />} />
  <Route
    path="/dashboard"
    element={
      <RequireAuth>
        <Dashboard />
      </RequireAuth>
    }
  />
</Routes>

# 3. 如何实现路由切换动画?

结合 React Router 和动画库(如 Framer Motion):

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

function AnimatedRoutes() {
  const location = useLocation();
  
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={location.pathname}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        transition={{ duration: 0.3 }}
      >
        <Routes location={location}>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/products/*" element={<Products />} />
        </Routes>
      </motion.div>
    </AnimatePresence>
  );
}

# 总结

React Router v6 通过简化 API 和引入新的功能,大大改善了 React 应用中的路由体验。主要改进包括:

  1. 更简洁的 API 设计
  2. 更直观的嵌套路由
  3. 相对路由路径
  4. 改进的导航 API
  5. 更好的类型支持
  6. 数据加载和表单处理的内置支持(v6.4+)

随着 React 生态系统的发展,React Router v6 与 React 18 和新的 Suspense 特性更好地集成,为构建现代 React 应用提供了强大的路由解决方案。