React Router

为什么需要路由?

单页应用(SPA):在单页面中实现多视图切换,避免整页刷新。

核心功能

  • 根据 URL 路径渲染对应组件。

  • 实现页面间导航(前进、后退、跳转)。

  • 支持动态路由、嵌套路由、路由守卫等。


一、RouterProvider 核心概念

1. 定位与作用

  • React Router v6.4+ 新增特性 :用于替代传统的 <BrowserRouter>,提供 数据驱动路由(Data Routing)能力。

  • 核心能力

    • 集中式路由配置:路由定义与组件解耦。

    • 数据预加载 :通过 loader 在路由匹配时自动加载数据。

    • 表单处理 :通过 action 处理表单提交。

    • 内置优化:流式渲染(Suspense)、错误边界、滚动恢复等。

  • 适用场景:中大型项目、需复杂数据流管理、服务端渲染(SSR)准备。


二、基础配置与使用

1. 安装依赖

复制代码
npm install react-router-dom

2. 创建路由配置

javascript 复制代码
// 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. 在入口文件中使用

javascript 复制代码
// 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>
);

三、数据流管理(loaderaction

1. loader:路由数据预加载

  • 触发时机:路由匹配时自动调用,在组件渲染前完成数据加载。

  • 典型场景:API 请求、权限校验、数据初始化。

javascript 复制代码
// 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")。

  • 典型场景:用户注册、数据修改、文件上传。

javascript 复制代码
// 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。

javascript 复制代码
// 全局错误页面配置
{
  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. 路由懒加载与代码分割

javascript 复制代码
// 结合 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. 权限控制与路由守卫

javascript 复制代码
// 封装鉴权高阶路由配置
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> 组件。

javascript 复制代码
import { ScrollRestoration } from "react-router-dom";

function App() {
  return (
    <div>
      <ScrollRestoration />
      {/* 其他内容 */}
    </div>
  );
}

五、与传统 BrowserRouter 对比

功能 RouterProvider BrowserRouter
路由配置 集中式(JSON 结构) 分散式(组件树中声明)
数据加载 内置 loader,自动预加载 需手动使用 useEffect
表单处理 内置 action,统一管理提交逻辑 需自行处理表单事件和状态
错误处理 全局/局部错误边界,自动捕获 需手动实现错误边界
代码分割 天然支持 Suspense + 懒加载 需手动配置
服务端渲染 更友好(数据预加载机制) 需额外配置

方案一:使用 createBrowserRouter + RouterProvider(推荐)

javascript 复制代码
// 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 组件方式

javascript 复制代码
// 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 参数或搜索参数传递,或在跳转时携带状态:

    javascript 复制代码
    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 逻辑?

  • 方案:封装为共享函数:

    javascript 复制代码
    // 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 定义类型:

    javascript 复制代码
    import { LoaderFunctionArgs, ActionFunctionArgs } from "react-router-dom";
    
    export async function loader({ params }: LoaderFunctionArgs) {
      // params 自动推断为 { id: string }
    }

七、总结

何时选择 RouterProvider

  • 大型应用:需要集中管理路由和数据流。

  • 复杂数据依赖:多路由共享数据、预加载需求。

  • 优化需求:流式渲染、代码分割、滚动恢复。

  • 未来扩展:计划迁移到服务端渲染(SSR)。

最佳实践

  1. 路由分层管理:拆分为多个路由配置文件。

  2. 严格类型定义:结合 TypeScript 提升安全性。

  3. 合理拆分组件:保持路由配置简洁清晰。

  4. 性能监控 :结合 Suspense 和懒加载优化首屏速度。


相关推荐
慧一居士37 分钟前
flex 布局完整功能介绍和示例演示
前端
DoraBigHead39 分钟前
小哆啦解题记——两数失踪事件
前端·算法·面试
一斤代码6 小时前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子6 小时前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年6 小时前
从前端转go开发的学习路线
前端·学习·golang
中微子7 小时前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina7 小时前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路8 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_8 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
甜瓜看代码8 小时前
1.
react.js·node.js·angular.js