为什么需要路由?
单页应用(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.stateconst 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
和懒加载优化首屏速度。