React 第五十九节 Router中 createBrowserRouter使用详解与案例分析

发布于:2025-06-13 ⋅ 阅读:(22) ⋅ 点赞:(0)

前言

createBrowserRouterReact Router v6.4+ 中用于创建浏览器端路由的核心函数,它引入了数据路由(data routing)的概念,提供了更强大的路由功能,如数据预加载、表单处理等。

一、createBrowserRouter 的主要用途

  1. 创建路由配置:定义应用程序的路由结构
  2. 数据预加载:在渲染组件前获取所需数据
  3. 表单处理:处理表单提交和数据变更
  4. 错误处理:提供路由级错误边界
  5. 嵌套路由:支持复杂布局和嵌套路由结构
  6. 延迟加载:实现代码分割和按需加载

二、完整代码示例

2.1、 基础路由配置

// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import {
  createBrowserRouter,
  RouterProvider,
  Link,
  Outlet
} from 'react-router-dom';

// 页面组件
function Home() {
  return (
    <div className="page">
      <h1>首页</h1>
      <p>欢迎来到我们的网站!</p>
      <nav className="page-nav">
        <Link to="/products" className="nav-link">产品列表</Link>
        <Link to="/about" className="nav-link">关于我们</Link>
      </nav>
    </div>
  );
}

function About() {
  return (
    <div className="page">
      <h1>关于我们</h1>
      <p>我们是一家专注于前端技术的公司。</p>
      <Link to="/" className="back-link">返回首页</Link>
    </div>
  );
}

// 产品列表组件
function Products() {
  return (
    <div className="page">
      <h1>产品列表</h1>
      <ul className="product-list">
        <li><Link to="/products/1">产品 1</Link></li>
        <li><Link to="/products/2">产品 2</Link></li>
        <li><Link to="/products/3">产品 3</Link></li>
      </ul>
      <Link to="/" className="back-link">返回首页</Link>
    </div>
  );
}

// 布局组件
function RootLayout() {
  return (
    <div className="app">
      <header className="app-header">
        <h1 className="logo">React Router 示例</h1>
        <nav className="main-nav">
          <Link to="/" className="nav-item">首页</Link>
          <Link to="/about" className="nav-item">关于</Link>
          <Link to="/products" className="nav-item">产品</Link>
        </nav>
      </header>
      
      <main className="app-content">
        {/* 子路由将在这里渲染 */}
        <Outlet />
      </main>
      
      <footer className="app-footer">
        <p>© 2023 React Router 示例应用</p>
      </footer>
    </div>
  );
}

// 创建路由配置
const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        index: true, // 匹配根路径
        element: <Home />
      },
      {
        path: "about",
        element: <About />
      },
      {
        path: "products",
        element: <Products />
      }
    ]
  }
]);

// 渲染应用
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

2.2、 高级功能:数据加载和表单处理

// 添加以下代码到 main.jsx

// 产品详情组件(带数据加载)
import { useLoaderData, useParams, Form, redirect } from 'react-router-dom';

function ProductDetail() {
  const product = useLoaderData();
  
  return (
    <div className="page">
      <h1>{product.name}</h1>
      <p className="product-description">{product.description}</p>
      <p className="product-price">价格: ¥{product.price}</p>
      
      <div className="product-actions">
        <Link to="/products" className="back-link">返回产品列表</Link>
        
        {/* 删除产品表单 */}
        <Form method="post" action={`/products/${product.id}/delete`}>
          <button type="submit" className="delete-btn">删除产品</button>
        </Form>
      </div>
      
      <div className="reviews-section">
        <h2>用户评价</h2>
        {/* 添加评价表单 */}
        <Form method="post" action={`/products/${product.id}/review`}>
          <div className="form-group">
            <label htmlFor="rating">评分:</label>
            <select id="rating" name="rating" required>
              <option value="5">5</option>
              <option value="4">4</option>
              <option value="3">3</option>
              <option value="2">2</option>
              <option value="1">1</option>
            </select>
          </div>
          <div className="form-group">
            <label htmlFor="comment">评论:</label>
            <textarea id="comment" name="comment" required></textarea>
          </div>
          <button type="submit" className="submit-btn">提交评价</button>
        </Form>
      </div>
    </div>
  );
}

// 产品数据加载器
export async function productLoader({ params }) {
  // 模拟API请求
  const products = {
    1: { id: 1, name: 'React 教程', description: '深入学习 React 框架', price: 99 },
    2: { id: 2, name: 'Node.js 实战', description: '构建高性能后端应用', price: 129 },
    3: { id: 3, name: 'TypeScript 指南', description: '掌握类型安全的JavaScript', price: 89 }
  };
  
  // 模拟延迟
  await new Promise(resolve => setTimeout(resolve, 500));
  
  const product = products[params.productId];
  if (!product) {
    throw new Response('产品未找到', { status: 404 });
  }
  
  return product;
}

// 删除产品操作
export async function deleteProductAction({ params }) {
  // 在实际应用中,这里会调用API删除产品
  console.log(`删除产品 ${params.productId}`);
  
  // 重定向到产品列表
  return redirect('/products');
}

// 添加评价操作
export async function addReviewAction({ request, params }) {
  const formData = await request.formData();
  const review = {
    productId: params.productId,
    rating: formData.get('rating'),
    comment: formData.get('comment'),
    date: new Date().toISOString()
  };
  
  // 在实际应用中,这里会保存评价到数据库
  console.log('添加评价:', review);
  
  // 重定向回产品详情页
  return redirect(`/products/${params.productId}`);
}

// 错误页面组件
function ErrorPage() {
  return (
    <div className="error-page">
      <h1>出错了!</h1>
      <p>抱歉,您访问的页面不存在或发生错误。</p>
      <Link to="/" className="back-link">返回首页</Link>
    </div>
  );
}

// 更新路由配置
const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />, // 全局错误边界
    children: [
      {
        index: true,
        element: <Home />
      },
      {
        path: "about",
        element: <About />
      },
      {
        path: "products",
        element: <Products />,
        children: [
          {
            path: ":productId",
            element: <ProductDetail />,
            loader: productLoader, // 数据预加载
            errorElement: <div className="error">产品加载失败</div>, // 特定路由错误边界
            children: [
              {
                path: "delete",
                action: deleteProductAction // 删除操作
              },
              {
                path: "review",
                action: addReviewAction // 添加评价操作
              }
            ]
          }
        ]
      }
    ]
  }
]);

2.3、 延迟加载(代码分割)

// 更新路由配置,添加延迟加载
const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      {
        index: true,
        element: <Home />
      },
      {
        path: "about",
        // 延迟加载关于页面
        lazy: () => import("./routes/About")
      },
      {
        path: "products",
        element: <Products />,
        children: [
          {
            path: ":productId",
            // 延迟加载产品详情
            lazy: async () => {
              const { default: ProductDetail } = await import("./routes/ProductDetail");
              return { 
                Component: ProductDetail,
                loader: productLoader
              };
            },
            errorElement: <div className="error">产品加载失败</div>,
            children: [
              // ...操作路由
            ]
          }
        ]
      },
      {
        path: "dashboard",
        // 延迟加载仪表盘
        lazy: () => import("./routes/Dashboard")
      }
    ]
  }
]);

三、createBrowserRouter 核心功能详解

3.1、 路由配置结构

createBrowserRouter 接收一个路由对象数组,每个对象包含:

  1. path:路由路径
  2. element:渲染的组件
  3. loader:数据加载函数
  4. action:表单处理函数
  5. errorElement:错误边界组件
  6. children:嵌套路由
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: "about", element: <About /> }
    ]
  }
]);

3.2、 数据加载(loader

在组件渲染前获取数据:

{
  path: "products/:id",
  element: <ProductDetail />,
  loader: async ({ params }) => {
    const response = await fetch(`/api/products/${params.id}`);
    if (!response.ok) throw new Error('产品未找到');
    return response.json();
  }
}

在组件中使用数据:

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

function ProductDetail() {
  const product = useLoaderData();
  return <h1>{product.name}</h1>;
}

3.3、 表单处理(action

处理表单提交:

{
  path: "products/:id/review",
  action: async ({ request, params }) => {
    const formData = await request.formData();
    const review = Object.fromEntries(formData);
    
    // 保存评价
    await saveReview(params.id, review);
    
    // 重定向回产品页
    return redirect(`/products/${params.id}`);
  }
}

在表单组件中使用:

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

function ReviewForm() {
  return (
    <Form method="post" action="/products/123/review">
      {/* 表单字段 */}
    </Form>
  );
}

3.4、 错误处理

{
  path: "/",
  element: <Layout />,
  errorElement: <ErrorBoundary />, // 全局错误边界
  children: [
    {
      path: "products/:id",
      element: <ProductDetail />,
      errorElement: <ProductError />, // 特定路由错误边界
      loader: productLoader
    }
  ]
}

在错误边界组件中获取错误信息:

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

function ErrorBoundary() {
  const error = useRouteError();
  return <div>错误: {error.message}</div>;
}

3.5、 路由导航

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

// 创建链接
<Link to="/about">关于我们</Link>

// 活动链接样式
<NavLink 
  to="/products"
  className={({ isActive }) => isActive ? 'active' : ''}
>
  产品
</NavLink>

// 编程式导航
function Home() {
  const navigate = useNavigate();
  
  return (
    <button onClick={() => navigate('/about')}>
      前往关于页面
    </button>
  );
}

四、createBrowserRouter 与传统路由对比

在这里插入图片描述

五、最佳实践

  1. 合理组织路由结构:使用嵌套路由处理复杂布局

  2. 利用代码分割:延迟加载非关键路由

  3. 统一错误处理:设置全局和局部错误边界

  4. 优化数据加载:

    使用Promise.all并行加载数据

    实现数据缓存

  5. 安全考虑:

    验证用户输入

    处理敏感数据

  6. 性能优化:

    使用defer API处理慢速加载

    实现加载状态指示器

总结

createBrowserRouter 是 React Router v6.4+ 中推荐的浏览器端路由创建方式,它通过数据驱动的路由模型提供了更强大的功能:

  1. 声明式路由配置:集中管理所有路由规则
  2. 内置数据加载:在渲染前预取所需数据
  3. 高效表单处理:简化表单提交逻辑
  4. 精细错误控制:路由级错误边界
  5. 无缝代码分割:优化应用性能

通过结合 RouterProvider 使用,可以构建功能丰富性能优越的现代 React 应用程序,特别适合需要复杂数据管理状态同步的应用场景。