为什么需要路由?
单页应用(SPA):在单页面中实现多视图切换,避免整页刷新。
核心功能:
-
根据 URL 路径渲染对应组件。
-
实现页面间导航(前进、后退、跳转)。
-
支持动态路由、嵌套路由、路由守卫等。
一、RouterProvider
核心概念
1. 定位与作用
-
React Router v6.4+ 新增特性:用于替代传统的
<BrowserRouter>
,提供 数据驱动路由(Data Routing)能力。 -
核心能力:
-
集中式路由配置:路由定义与组件解耦。
-
数据预加载:通过
loader
在路由匹配时自动加载数据。 -
表单处理:通过
action
处理表单提交。 -
内置优化:流式渲染(Suspense)、错误边界、滚动恢复等。
-
-
适用场景:中大型项目、需复杂数据流管理、服务端渲染(SSR)准备。
二、基础配置与使用
1. 安装依赖
npm install react-router-dom
2. 创建路由配置
// src/router.js
import { createBrowserRouter } from "react-router-dom";
import App from "./App";
import Home from "./pages/Home";
import UserProfile, { loader as userLoader } from "./pages/UserProfile";
// 定义路由配置对象
export const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{ index: true, element: <Home /> },
{
path: "users/:id",
element: <UserProfile />,
loader: userLoader, // 数据预加载
errorElement: <ErrorPage />, // 错误边界
},
],
},
]);
3. 在入口文件中使用
// src/main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import { router } from "./router";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
三、数据流管理(loader
与 action
)
1. loader
:路由数据预加载
-
触发时机:路由匹配时自动调用,在组件渲染前完成数据加载。
-
典型场景:API 请求、权限校验、数据初始化。
// src/pages/UserProfile.jsx
export async function loader({ params, request }) {
// 获取动态参数(如用户ID)
const userId = params.id;
// 发起数据请求
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error("用户不存在"); // 抛出错误会被 errorElement 捕获
}
return response.json();
}
function UserProfile() {
// 使用 useLoaderData 获取 loader 返回的数据
const userData = useLoaderData();
return <div>{userData.name}</div>;
}
2. action
:处理表单提交
-
触发时机:表单提交时自动调用(需设置
method="post"
)。 -
典型场景:用户注册、数据修改、文件上传。
// src/pages/EditUser.jsx
export async function action({ request, params }) {
const formData = await request.formData();
const updates = Object.fromEntries(formData);
// 提交数据到后端
await fetch(`/api/users/${params.id}`, {
method: "PUT",
body: JSON.stringify(updates),
});
// 重定向到用户详情页
return redirect(`/users/${params.id}`);
}
function EditUser() {
return (
<Form method="post">
<input name="name" type="text" />
<button type="submit">保存</button>
</Form>
);
}
四、高级功能与最佳实践
1. 错误处理与边界
-
全局错误捕获:通过路由配置的
errorElement
统一处理。 -
局部错误处理:在组件内使用
useRouteError
Hook。
// 全局错误页面配置
{
path: "/users/:id",
element: <UserProfile />,
loader: userLoader,
errorElement: <ErrorBoundary />, // 自定义错误组件
}
// ErrorBoundary.jsx
import { useRouteError } from "react-router-dom";
function ErrorBoundary() {
const error = useRouteError();
return (
<div>
<h1>出错了!</h1>
<p>{error.message}</p>
</div>
);
}
2. 路由懒加载与代码分割
// 结合 React.lazy 和 Suspense 实现懒加载
import { lazy, Suspense } from "react";
const Settings = lazy(() => import("./pages/Settings"));
const router = createBrowserRouter([
{
path: "/settings",
element: (
<Suspense fallback={<div>加载中...</div>}>
<Settings />
</Suspense>
),
},
]);
3. 权限控制与路由守卫
// 封装鉴权高阶路由配置
function protectedLoader({ request }) {
const isAuthenticated = checkAuth();
if (!isAuthenticated) {
return redirect("/login");
}
return null;
}
const router = createBrowserRouter([
{
path: "/dashboard",
element: <Dashboard />,
loader: protectedLoader, // 路由加载前鉴权
},
]);
4. 滚动恢复与定位
-
自动恢复:React Router 默认记录滚动位置。
-
手动控制:使用
<ScrollRestoration>
组件。
import { ScrollRestoration } from "react-router-dom";
function App() {
return (
<div>
<ScrollRestoration />
{/* 其他内容 */}
</div>
);
}
五、与传统 BrowserRouter
对比
功能 | RouterProvider | BrowserRouter |
---|---|---|
路由配置 | 集中式(JSON 结构) | 分散式(组件树中声明) |
数据加载 | 内置 loader ,自动预加载 | 需手动使用 useEffect |
表单处理 | 内置 action ,统一管理提交逻辑 | 需自行处理表单事件和状态 |
错误处理 | 全局/局部错误边界,自动捕获 | 需手动实现错误边界 |
代码分割 | 天然支持 Suspense + 懒加载 | 需手动配置 |
服务端渲染 | 更友好(数据预加载机制) | 需额外配置 |
方案一:使用 createBrowserRouter + RouterProvider(推荐)
// router.js
import { createBrowserRouter } from "react-router-dom"
import App from "./App"
import Home from "./pages/home"
import News from "./pages/news"
export default createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{
index: true,
element: <Home />
},
{
path: '/news',
element: <News />
}
]
}
])
// App.jsx
import './app.css'
import { Link, Outlet } from 'react-router-dom'
function App() {
return (
<div className='main'>
<nav>
<Link to="/">Home</Link> {/* 修改为根路径 */}
<Link to="/news">News</Link>
</nav>
<div className="content">
<Outlet />
</div>
</div>
)
}
// main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router/router'
import { Provider } from 'react-redux'
import store from './store/index.js'
createRoot(document.getElementById('root')).render(
<StrictMode>
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
</StrictMode>
)
方案二:使用 BrowserRouter 组件方式
// main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import store from './store/index.js'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
createRoot(document.getElementById('root')).render(
<StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</StrictMode>
)
// App.jsx
import './app.css'
import { Link, Outlet, Routes, Route } from 'react-router-dom'
import Home from './pages/home'
import News from './pages/news'
function App() {
return (
<div className='main'>
<nav>
<Link to="/">Home</Link>
<Link to="/news">News</Link>
</nav>
<div className="content">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/news" element={<News />} />
</Routes>
</div>
</div>
)
}
六、常见问题与解决方案
1. 如何传递自定义参数到 loader
?
-
方案:通过 URL 参数或搜索参数传递,或在跳转时携带状态:
navigate("/users/123", { state: { from: "home" } }); // loader 中获取 export async function loader({ request }) { const url = new URL(request.url); const from = url.searchParams.get("from"); // 或通过 location.state const state = useLocation().state; }
2. 如何复用 loader
逻辑?
-
方案:封装为共享函数:
// utils/loaders.js export async function loadUserData(userId) { const res = await fetch(`/api/users/${userId}`); return res.json(); } // 路由配置 import { loadUserData } from "./utils/loaders"; loader: ({ params }) => loadUserData(params.id)
3. 如何结合 TypeScript 使用?
-
类型定义:为
loader
/action
定义类型:import { LoaderFunctionArgs, ActionFunctionArgs } from "react-router-dom"; export async function loader({ params }: LoaderFunctionArgs) { // params 自动推断为 { id: string } }
七、总结
何时选择 RouterProvider
?
-
大型应用:需要集中管理路由和数据流。
-
复杂数据依赖:多路由共享数据、预加载需求。
-
优化需求:流式渲染、代码分割、滚动恢复。
-
未来扩展:计划迁移到服务端渲染(SSR)。
最佳实践
-
路由分层管理:拆分为多个路由配置文件。
-
严格类型定义:结合 TypeScript 提升安全性。
-
合理拆分组件:保持路由配置简洁清晰。
-
性能监控:结合
Suspense
和懒加载优化首屏速度。