React Router V7 完全教程:从入门到精通
本教程面向零基础到进阶的 React 开发者,用生活化语言系统讲解 React Router V7 的全套用法。所有代码均使用 React 18+ 函数式组件,兼容 V7 最新 API,可直接运行。
一、环境搭建与依赖安装
1.1 创建 React 项目
推荐使用 Vite(速度快、配置少):
# npm
npm create vite@latest react-router-demo -- --template react
# yarn
yarn create vite react-router-demo --template react
# pnpm
pnpm create vite react-router-demo --template react
进入项目目录:
cd react-router-demo
1.2 安装 React Router V7
React Router V7 的核心依赖只有 react-router 一个包,不再需要单独安装 @types/react-router-dom 等额外包。
# npm
npm install react-router
# yarn
yarn add react-router
# pnpm
pnpm add react-router
V7 关键变化:V7 将 V6 中分离的 react-router-dom 和 react-router 合并为一个统一的 react-router 包。不再需要安装 react-router-dom。
1.3 验证安装是否成功
检查 package.json 中是否包含:
{
"dependencies": {
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-router": "^7.0.0"
}
}
1.4 最小化启动示例
// main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router";
// 临时首页
function Home() {
return <h1>React Router V7 教程</h1>;
}
const router = createBrowserRouter([
{ path: "/", Component: Home, index: true },
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
启动项目验证:
npm run dev
浏览器打开 http://localhost:5173,看到"React Router V7 教程"即表示安装成功。
二、路由初体验:两个页面的切换
2.1 创建页面组件
// pages/Home.jsx
function Home() {
return <h1>首页</h1>;
}
export default Home;
// pages/About.jsx
function About() {
return <h1>关于页</h1>;
}
export default About;
2.2 配置路由表
// router.jsx
import { createBrowserRouter } from "react-router";
import Home from "./pages/Home";
import About from "./pages/About";
export const router = createBrowserRouter([
{ path: "/", Component: Home }, // 路径 / 渲染 Home
{ path: "/about", Component: About }, // 路径 /about 渲染 About
]);
2.3 挂载路由
// main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RouterProvider } from "react-router";
import { router } from "./router";
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
踩坑提醒:V7 使用
RouterProvider+createBrowserRouter的组合方式。V6 中常见的<BrowserRouter>+<Routes>JSX 写法在 V7 中不再是推荐的顶层方式(但 V7 仍兼容该写法)。
2.4 添加导航链接
// App.jsx
import { Link, NavLink, Outlet } from "react-router";
function App() {
return (
<div>
<nav>
{/* Link:基础跳转,不会高亮 */}
<Link to="/">首页</Link>
{/* NavLink:自动添加 active 类名,适合导航栏 */}
<NavLink
to="/about"
className={({ isActive }) => (isActive ? "active" : "")}
>
关于
</NavLink>
</nav>
{/* Outlet:子路由的渲染出口 */}
<Outlet />
</div>
);
}
export default App;
三、路由失败处理
3.1 404 页面配置
在路由表末尾添加兜底路由:
// router.jsx
import { createBrowserRouter } from 'react-router'
// ... 其他路由
{ path: '*', Component: NotFound } // 放在最后,拦截所有未匹配路径
// pages/NotFound.jsx
import { Link } from "react-router";
function NotFound() {
return (
<div style={{ textAlign: "center", padding: 50 }}>
<h1>404 - 页面未找到</h1>
<p>
你访问的页面不存在,返回 <Link to="/">首页</Link>
</p>
</div>
);
}
export default NotFound;
3.2 路由鉴权失败的跳转
创建鉴权守卫组件
// components/AuthGuard.jsx
import { Navigate, useLocation } from "react-router";
function AuthGuard({ children }) {
const location = useLocation();
// 模拟判断:实际项目中从 store 或 cookie 中读取
const isLoggedIn = localStorage.getItem("token");
if (!isLoggedIn) {
// 记住用户想去的路径,登录后跳回
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
export default AuthGuard;
路由中应用鉴权
// router.jsx
<Route
path="/admin"
element={
<AuthGuard>
<AdminPage />
</AuthGuard>
}
/>
3.3 全局错误边界
React Router V7 支持 errorElement 属性,在路由表级别捕获所有异常。
// pages/ErrorPage.jsx
import { useRouteError, Link } from "react-router";
function ErrorPage() {
const error = useRouteError();
return (
<div style={{ textAlign: "center", padding: 50 }}>
<h1>页面出错了</h1>
<p>{error?.message || "未知错误"}</p>
<Link to="/">返回首页</Link>
</div>
);
}
export default ErrorPage;
// router.jsx - 根路由上配置全局错误页
import { createBrowserRouter } from "react-router";
import ErrorPage from "./pages/ErrorPage";
export const router = createBrowserRouter([
{
path: "/",
errorElement: <ErrorPage />, // 全局错误处理
children: [
// ...所有子路由
],
},
]);
生活化类比:
errorElement就像大楼里的消防系统——不管哪层着火(哪页出错),都会启动统一的灭火(错误展示)流程,不至于整栋大楼坍塌。
四、嵌套路由
4.1 什么是嵌套路由?
现实生活中,很多页面结构是嵌套的。比如一个后台管理系统:
/admin → 侧边栏(不变)
/admin/dashboard → 仪表盘内容
/admin/users → 用户管理内容
/admin/settings → 设置内容
侧边栏始终显示,但右侧内容区根据 URL 切换不同的子页面。这就是嵌套路由的典型场景。
4.2 <Outlet /> — 子路由的"插槽"
<Outlet /> 就像一面智能墙,在父组件中挖了一个洞,子路由匹配时自动将子组件填入这个洞。
// pages/AdminLayout.jsx
import { NavLink, Outlet } from "react-router";
function AdminLayout() {
return (
<div style={{ display: "flex" }}>
{/* 侧边栏:始终显示 */}
<nav style={{ width: 200, background: "#f0f0f0", padding: 20 }}>
<NavLink to="/admin/dashboard">仪表盘</NavLink>
<br />
<NavLink to="/admin/users">用户管理</NavLink>
<br />
<NavLink to="/admin/settings">系统设置</NavLink>
</nav>
{/* 内容区:子路由内容会渲染在这里 */}
<main style={{ flex: 1, padding: 20 }}>
<Outlet />
</main>
</div>
);
}
export default AdminLayout;
4.3 配置多级嵌套路由
// router.jsx
import { createBrowserRouter } from "react-router";
import App from "./App";
import AdminLayout from "./pages/AdminLayout";
import Dashboard from "./pages/Dashboard";
import Users from "./pages/Users";
import Settings from "./pages/Settings";
export const router = createBrowserRouter([
{
path: "/",
Component: App,
children: [
// 一级子路由放这里
],
},
{
path: "/admin",
Component: AdminLayout, // 父组件:侧边栏布局
children: [
{ path: "dashboard", Component: Dashboard }, // /admin/dashboard
{ path: "users", Component: Users }, // /admin/users
{ path: "settings", Component: Settings }, // /admin/settings
],
},
]);
踩坑提醒:子路由的
path不要写/前缀。path: 'dashboard'会自动拼接为/admin/dashboard。如果写成path: '/dashboard',则会变成根路径下的/dashboard,脱离嵌套结构。
五、路由跳转
5.1 声明式导航
声明式导航是指通过 JSX 组件声明跳转行为,不需要写 JavaScript 逻辑。
| 组件 | 用途 | 特色 |
|---|---|---|
<Link to="/page"> |
基础跳转 | 渲染为 <a> 标签,不会刷新页面 |
<NavLink> |
导航栏跳转 | 自动感知当前 URL,可应用高亮样式 |
<Navigate to="/page"> |
重定向 | 渲染时立即跳转,常用于鉴权跳转 |
// 基础跳转
<Link to="/about">去关于页</Link>
// 带高亮的导航跳转
<NavLink
to="/admin/users"
className={({ isActive }) => isActive ? 'active-link' : ''}
>
用户管理
</NavLink>
// 条件跳转(鉴权失败时)
{!isLoggedIn && <Navigate to="/login" replace />}
5.2 编程式导航(useNavigate)
编程式导航是在 JavaScript 代码中主动触发跳转,适合"点击按钮后先处理逻辑再跳转"的场景。
import { useNavigate } from "react-router";
function LoginPage() {
const navigate = useNavigate();
const handleLogin = async () => {
// 先执行业务逻辑
const success = await loginApi({ username, password });
if (success) {
navigate("/dashboard"); // 前进到新页面
// navigate(-1) // 返回上一页
// navigate('/', { replace: true }) // 替换当前历史记录,不可回退
}
};
return <button onClick={handleLogin}>登录</button>;
}
5.3 useLocation — 获取当前路由信息
useLocation 返回包含当前路径、搜索参数、路由状态的对象。
import { useLocation } from "react-router";
function CurrentPath() {
const location = useLocation();
return (
<div>
<p>当前路径:{location.pathname}</p>
<p>搜索参数:{location.search}</p>
<p>路由状态:{JSON.stringify(location.state)}</p>
</div>
);
}
5.4 带参数跳转
// 传路由状态(state)
navigate("/dashboard", { state: { from: "login", timestamp: Date.now() } });
// 在目标页面读取
const location = useLocation();
console.log(location.state?.from); // 'login'
踩坑提醒:
useLocation().state仅在本次路由跳转中有效,刷新页面后state会丢失。需要持久化就用useSearchParams(体现在 URL 上)或 Store(如 Zustand)。
六、Loader 与 Action
6.1 什么是 Loader?
Loader 是在路由渲染之前执行的函数,用来提前获取页面所需的数据。
类比:去餐厅吃饭。你点完菜(访问路由),服务员(Loader)会先根据你的要求从后厨拿取食材(从后端获取数据),准备好了再给你上菜(渲染页面)。你不需要先看到空盘子再等菜。
6.2 Loader 使用示例:用户列表
定义 Loader
// pages/users/loader.js
export async function usersLoader() {
// 模拟从 API 获取数据
const res = await fetch("https://jsonplaceholder.typicode.com/users");
if (!res.ok) throw new Error("用户列表加载失败");
return { users: await res.json() };
}
路由配置中挂载 Loader
// router.jsx
{
path: '/users',
Component: UsersPage,
loader: usersLoader, // 路由渲染前执行
}
组件中使用 useLoaderData 获取数据
// pages/UsersPage.jsx
import { useLoaderData } from "react-router";
function UsersPage() {
const { users } = useLoaderData(); // 获取 loader 返回的数据
return (
<div>
<h1>用户列表</h1>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
export default UsersPage;
6.3 什么是 Action?
Action 是个可以修改后端数据的动态路由的专用路由操作。V7 中 Action 的处理方式有所调整:
Action 是路由的 action 函数,在表单提交(<Form>)或调用 useSubmit 时触发,用来处理数据的增删改操作。
// pages/users/action.js
export async function usersAction({ request }) {
const formData = await request.formData();
const username = formData.get("username");
// 模拟提交到 API
const res = await fetch("https://jsonplaceholder.typicode.com/users", {
method: "POST",
body: JSON.stringify({ name: username }),
headers: { "Content-Type": "application/json" },
});
if (!res.ok) throw new Error("提交失败");
return { success: true, data: await res.json() };
}
// router.jsx
{
path: '/users',
Component: UsersPage,
loader: usersLoader,
action: usersAction, // 表单提交时执行
}
// pages/UsersPage.jsx
import { Form, useLoaderData, useActionData } from "react-router";
function UsersPage() {
const { users } = useLoaderData();
const actionData = useActionData(); // Action 返回的数据
return (
<div>
{/* Form 提交会自动调用路由的 action */}
<Form method="post">
<input name="username" placeholder="新增用户名" />
<button type="submit">添加</button>
</Form>
{actionData?.success && <p>添加成功!</p>}
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
</div>
);
}
6.4 Loader 与 Action 的执行时机总结
| 函数 | 触发时机 | 获取数据 |
|---|---|---|
loader |
路由渲染前(GET 类操作) | useLoaderData() |
action |
表单提交(POST/PUT/DELETE) | useActionData() |
踩坑提醒:
loader和action里抛出错误会被路由的errorElement捕获,所以不必在组件里写 try-catch,把错误处理交给路由层统一管理即可。
七、路由参数与状态获取
7.1 动态路由参数:useParams()
用于从 URL 路径中提取变量,比如 /users/123 中的 123。
// router.jsx
{ path: '/users/:userId', Component: UserDetail }
// pages/UserDetail.jsx
import { useParams, useNavigate } from "react-router";
function UserDetail() {
const { userId } = useParams(); // 从 URL 中提取 userId
const navigate = useNavigate();
return (
<div>
<h1>用户详情 #{userId}</h1>
<button onClick={() => navigate(-1)}>返回列表</button>
</div>
);
}
适用场景:用户详情、文章详情、商品页面等——URL 本身就代表唯一资源。
7.2 搜索参数(Query String):useSearchParams()
用于 URL 中 ? 后面的键值对参数,如 /search?keyword=react&page=2。
import { useSearchParams } from "react-router";
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
// 读取参数
const keyword = searchParams.get("keyword") || "";
const page = searchParams.get("page") || "1";
// 更新参数(会触发 URL 变化和重新渲染)
const handleSearch = (newKeyword) => {
setSearchParams({ keyword: newKeyword, page: "1" });
};
return (
<div>
<input
value={keyword}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索..."
/>
<p>
当前搜索:{keyword},第 {page} 页
</p>
</div>
);
}
适用场景:搜索页、列表筛选、分页——参数变化反映了用户的操作意图。
7.3 路由状态(State):navigate 传参
不体现在 URL 上,通过内存传递的数据。
// 发送方
navigate("/result", { state: { score: 95, name: "张三" } });
// 接收方
import { useLocation } from "react-router";
const location = useLocation();
console.log(location.state?.score); // 95
适用场景:表单提交成功后的结果页、登录后跳回的来源页——不需要暴露在 URL 中的临时数据。
7.4 三种参数的对比
| 方式 | 数据存在哪里 | 刷新后是否保留 | 适用场景 |
|---|---|---|---|
useParams() |
URL 路径 | 是 | 资源标识(如用户 ID) |
useSearchParams() |
URL 查询串 | 是 | 搜索、筛选、分页 |
state |
浏览器内存 | 否 | 临时跨页面传递 |
八、默认子路由与子路由错误页
8.1 默认子路由(Index Route)
当访问父路径 /admin 时,需要自动展示一个默认页面(如仪表盘),这就是 index 路由的用途。
// router.jsx
{
path: '/admin',
Component: AdminLayout,
children: [
{ index: true, Component: Dashboard }, // 默认子路由,访问 /admin 时展示
{ path: 'users', Component: Users },
{ path: 'settings', Component: Settings },
],
}
当用户访问 /admin 时,AdminLayout 渲染,<Outlet /> 处自动显示 Dashboard 组件。
与
path: ''的区别:V7 推荐使用index: true声明默认子路由,语义更清晰。
8.2 子路由专属错误页
不同模块可能需要不同的错误展示方式。V7 支持为每个路由子树设置独立的 errorElement。
// router.jsx
{
path: '/admin',
Component: AdminLayout,
errorElement: <AdminErrorPage />, // admin 子树专属错误页
children: [
{ index: true, Component: Dashboard },
{ path: 'users', Component: Users, errorElement: <UserErrorPage /> },
// users 路由还可以有自己的错误页,覆盖父级
],
},
{
path: '/shop',
Component: ShopLayout,
errorElement: <ShopErrorPage />, // shop 子树专属错误页
children: [...],
}
// pages/AdminErrorPage.jsx
import { useRouteError, Link } from "react-router";
function AdminErrorPage() {
const error = useRouteError();
return (
<div style={{ padding: 20 }}>
<h2>管理后台数据加载失败</h2>
<p>{error?.message}</p>
<Link to="/">返回首页</Link>
</div>
);
}
export default AdminErrorPage;
业务价值:不同模块的错误提示风格统一 —— 比如管理后台的错误页显示"请联系管理员",而商城模块的错误页显示"商品加载失败,返回浏览"。
九、路由懒加载
9.1 为什么要懒加载?
当应用有几十上百个页面时,打包后所有的 JS 代码会塞进一个巨大的文件里。用户打开首页时,实际上也把"设置页""用户管理页"等根本没访问的代码下载了 —— 白屏时间变长。
懒加载的原理:按路由拆分代码包。用户访问 /dashboard 只下载 Dashboard 页面所需的代码,访问 /settings 时才下载 Settings 的代码。
9.2 React.lazy() + Suspense 实现
// router.jsx
import { createBrowserRouter } from "react-router";
import { lazy, Suspense } from "react";
// 懒加载路由组件
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
V7 推荐方式 —— 使用 lazy 属性的路由级懒加载:
// router.jsx
import { createBrowserRouter } from "react-router";
export const router = createBrowserRouter([
{
path: "/",
// 使用 lazy 属性直接懒加载组件
lazy: () => import("./App"),
},
{
path: "/about",
lazy: () => import("./pages/About"),
},
{
path: "/admin",
lazy: () => import("./pages/AdminLayout"),
children: [
{
index: true,
lazy: () => import("./pages/Dashboard"),
},
{
path: "users",
lazy: () => import("./pages/Users"),
},
],
},
]);
9.3 加载占位组件
// components/LoadingSpinner.jsx
function LoadingSpinner() {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
}}
>
<div
style={{
width: 40,
height: 40,
border: "4px solid #e0e0e0",
borderTopColor: "#1976d2",
borderRadius: "50%",
animation: "spin 1s linear infinite",
}}
/>
<span style={{ marginLeft: 12 }}>页面加载中...</span>
</div>
);
}
export default LoadingSpinner;
对应的 CSS(添加到全局样式):
@keyframes spin {
to {
transform: rotate(360deg);
}
}
9.4 使用 Suspense 包裹
// main.jsx
import { Suspense } from "react";
import { RouterProvider } from "react-router";
import LoadingSpinner from "./components/LoadingSpinner";
import { router } from "./router";
createRoot(document.getElementById("root")).render(
<Suspense fallback={<LoadingSpinner />}>
<RouterProvider router={router} />
</Suspense>,
);
9.5 懒加载失败的重试逻辑
当懒加载的网络请求失败(如 CDN 挂了、用户断网),需要给用户重试机会。
// main.jsx - 增加全局错误捕获
import { Component, Suspense } from "react";
class LazyErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>页面加载失败</h2>
<button onClick={() => window.location.reload()}>重新加载</button>
</div>
);
}
return this.props.children;
}
}
// 使用双层包裹
createRoot(document.getElementById("root")).render(
<LazyErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<RouterProvider router={router} />
</Suspense>
</LazyErrorBoundary>,
);
9.6 指定 chunk 名称便于调试
// 魔法注释:打包后生成 dashboard-xxx.js,方便在 Network 面板识别
lazy: () => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard'),
9.7 验证代码分割效果
- 打开浏览器 DevTools → Network 面板。
- 勾选 JS 类型过滤。
- 首次访问首页,观察加载了哪些 JS 文件。
- 点击导航到其他页面,观察是否有新的 JS 文件请求发出。
十、完整项目结构示例
react-router-demo/
├── index.html
├── package.json
├── src/
│ ├── main.jsx # 入口文件,挂载路由
│ ├── router.jsx # 路由表配置(核心)
│ ├── App.jsx # 根布局组件
│ ├── components/
│ │ ├── AuthGuard.jsx # 鉴权守卫
│ │ ├── ErrorBoundary.jsx # 错误边界
│ │ └── LoadingSpinner.jsx # 加载占位
│ └── pages/
│ ├── Home.jsx
│ ├── About.jsx
│ ├── NotFound.jsx
│ ├── ErrorPage.jsx # 全局错误页
│ ├── Login.jsx
│ ├── AdminLayout.jsx # 后台布局(含侧边栏)
│ ├── Dashboard.jsx
│ ├── Users.jsx
│ ├── UserDetail.jsx
│ └── Settings.jsx
└── vite.config.js
十一、常见踩坑汇总
| 问题 | 原因 | 解法 |
|---|---|---|
| 子路由不渲染 | 父组件中忘记写 <Outlet /> |
在父组件的 JSX 中加上 <Outlet /> |
| 跳转后页面空白 | 路由表中没有匹配的路径 | 检查 path 拼写,末尾加 path: '*' 兜底 |
useNavigate() 报错 |
组件不在 <RouterProvider> 子树中 |
确保该组件是路由表中的合法路由组件 |
| Loader 数据为 null | 忘记在组件中使用 useLoaderData() |
确认组件是路由表配置中的 Component |
| 懒加载后页面空白 | 没有用 <Suspense> 包裹 |
在 RouterProvider 外层加 Suspense fallback |
NavLink 高亮不生效 |
路径匹配规则不对 | to="/admin" 不会匹配 /admin/users,用 end 属性精确匹配 |
| 刷新后 state 丢失 | state 存在内存中 |
改用 useSearchParams() 或 localStorage 持久化 |
十二、V7 与旧版本核心差异速查
| 特性 | V6 写法 | V7 写法 |
|---|---|---|
| 创建路由 | createBrowserRouter() + <RouterProvider> |
相同,推荐写法 |
| 路由定义 | element: <Comp /> |
Component: Comp(大写 C,不用 JSX) |
| 默认子路由 | path: '' 或 index: true |
推荐 index: true |
| 数据获取 | loader / useLoaderData() |
相同,但 V7 加强了类型推导 |
| 包导入 | react-router-dom |
统一为 react-router |
| JSX 路由写法 | <BrowserRouter><Routes> |
仍兼容,但推荐 createBrowserRouter 配置式 |
十三、调试方法
13.1 查看当前路由状态
在浏览器控制台中安装 React DevTools,选择对应的组件后可以在右侧查看 props 中的 location、params 等信息。
13.2 启用路由日志
// 在 App 组件中临时添加
import { useLocation } from "react-router";
useEffect(() => {
console.log("路由变化:", location.pathname, location.search);
}, [location]);
13.3 常见报错的排查顺序
- 浏览器控制台有没有红色报错?
- 报错信息里的路径和路由表中是否一致?
<Outlet />是否渲染在正确的父组件中?loader返回的数据格式是否正确?
十四、课后练习题
练习 1:基础路由搭建
目标:创建一个包含"首页 / 关于 / 404"的 React Router V7 应用。
要求:
- 使用
createBrowserRouter配置路由表。 - 访问不存在的路径时显示 404 页面。
- 使用
NavLink实现导航高亮。 - 配置全局
errorElement。
练习 2:后台管理系统
目标:实现一个带侧边栏的后台管理系统。
要求:
/admin布局包含左侧导航(仪表盘、用户列表、设置)。- 访问
/admin时默认展示仪表盘。 - 用户列表页面使用
loader从 API 加载数据。 - 用户列表页面支持 "新增用户" 表单,使用
action提交数据。 - 用户列表页点击用户名进入
/admin/users/:id详情页。
练习 3:全功能综合项目
目标:综合运用本教程所有知识点。
要求:
- 路由懒加载所有页面组件,添加 loading 动画。
- 对
/admin路由配置鉴权守卫,未登录跳转到/login。 - 为不同模块配置独立的
errorElement。 - 添加搜索功能,搜索参数体现在 URL 的 query string 中。
- 提交表单后使用
state传递结果到成功页。 - 使用浏览器 Network 面板验证代码分割效果。
十五、练习题参考答案思路
练习 1 思路
npm create vite@latest创建项目,安装react-router。- 创建
Home.jsx、About.jsx、NotFound.jsx、ErrorPage.jsx。 - 用
createBrowserRouter配置四个路由。 errorElement挂在根路由。- 用
RouterProvider挂载到根节点。
练习 2 思路
- 创建
AdminLayout.jsx,左侧导航用NavLink,右侧放<Outlet />。 router.jsx中配置嵌套路由,index: true指向Dashboard。- 用户列表路由配置
loader和action。 Users.jsx中用useLoaderData()获取列表,用<Form method="post">提交新增。- 详情页路由
path: ':userId',用useParams()获取用户 ID。 - 详情页用
useNavigate(-1)返回。
练习 3 思路
- 所有页面用
lazy: () => import(...)懒加载。 main.jsx中用<Suspense>包裹<RouterProvider>。- 创建
AuthGuard.jsx,包裹需要鉴权的路由组件。 - 为
/admin和/shop分别设置errorElement。 - 搜索页用
useSearchParams()读写 query string。 - 提交后用
navigate('/success', { state: { ... } })传递结果。 - 打开 Network 面板,观察懒加载 chunk 的加载时机。
总结
React Router V7 是 React 生态中最主流的路由方案,其核心概念如下:
- 路由表:用
createBrowserRouter集中管理所有路由。 - 嵌套路由:用
children声明层级关系,用<Outlet />渲染子路由。 - 数据加载:用
loader预加载数据,用action处理表单提交。 - 错误处理:用
errorElement统一捕获路由层异常。 - 懒加载:用
lazy属性按路由拆分代码包,提升首屏性能。
课后请完成三个练习,逐步从零搭建一个完整的 React Router V7 应用。