React 第五十九节 Router中 createBrowserRouter使用详解与案例分析
前言
createBrowserRouter
是 React Router v6.4+
中用于创建浏览器端路由的核心函数,它引入了数据路由(data routing
)的概念,提供了更强大的路由功能,如数据预加载、表单处理等。
一、createBrowserRouter 的主要用途
- 创建路由配置:定义应用程序的路由结构
- 数据预加载:在渲染组件前获取所需数据
- 表单处理:处理表单提交和数据变更
- 错误处理:提供路由级错误边界
- 嵌套路由:支持复杂布局和嵌套路由结构
- 延迟加载:实现代码分割和按需加载
二、完整代码示例
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 接收一个路由对象数组,每个对象包含:
path
:路由路径element
:渲染的组件loader
:数据加载函数action
:表单处理函数errorElement
:错误边界组件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
与传统路由对比
五、最佳实践
-
合理组织路由结构:使用嵌套路由处理复杂布局
-
利用代码分割:延迟加载非关键路由
-
统一错误处理:设置全局和局部错误边界
-
优化数据加载:
使用Promise.all并行加载数据
实现数据缓存
-
安全考虑:
验证用户输入
处理敏感数据
-
性能优化:
使用defer API处理慢速加载
实现加载状态指示器
总结
createBrowserRouter
是 React Router v6.4+ 中推荐的浏览器端路由创建方式,它通过数据驱动的路由模型提供了更强大的功能:
- 声明式路由配置:集中管理所有路由规则
- 内置数据加载:在渲染前预取所需数据
- 高效表单处理:简化表单提交逻辑
- 精细错误控制:路由级错误边界
- 无缝代码分割:优化应用性能
通过结合 RouterProvider
使用,可以构建功能丰富、性能优越的现代 React 应用程序,特别适合需要复杂数据管理和状态同步
的应用场景。