React Router DOM 全面学习笔记:从原理到实战

React Router DOM 全面学习笔记:从原理到实战

在 React 生态中,react-router-dom 是实现前端路由、构建单页应用(SPA)的核心库。它解决了单页应用中页面切换、路由匹配、权限控制等关键问题,让前端开发能够脱离后端路由的强依赖,实现更流畅的用户体验。本文将从路由基础、核心用法、实战案例、原理剖析等维度,全面梳理 react-router-dom 的学习要点,结合代码示例深入讲解,助力开发者快速掌握并灵活运用。

一、前端路由的核心概念

1.1 路由的演变:从后端到前端

在前后端未分离的传统开发模式中,路由的控制权完全掌握在后端。前端仅负责页面切图与静态展示,当用户点击链接或输入 URL 时,会向服务器发送 HTTP 请求,后端根据请求路径匹配对应的资源,返回完整的 HTML 页面。这种模式存在明显弊端:每次页面切换都会重新加载整个页面,导致页面白屏、加载速度慢,用户体验较差,此时的前端开发者也被戏称为"切图仔"。

随着前后端分离架构的普及,前端技术栈日益成熟,HTML5 提供了原生的路由能力,前端路由应运而生。前端路由允许在不刷新整个页面的前提下,通过改变 URL 路径,实现页面组件的切换与内容更新。其核心逻辑是:URL 变化时,前端捕获该事件,通过路由规则匹配对应的组件,在页面中渲染新组件,从而实现"无刷新跳转",大幅提升用户体验。

1.2 前端路由的两种实现形式

react-router-dom 提供了两种主流的路由实现方式,分别基于不同的技术原理,适用于不同场景:

1.2.1 HashRouter:基于锚点的路由

HashRouter 利用 URL 中的 锚点(#) 实现路由跳转。锚点原本用于定位页面内的元素,其特性是:改变锚点内容不会触发浏览器的页面刷新,仅会触发 hashchange 事件。HashRouter 正是借助这一特性,将路由信息存储在锚点之后,例如 http://localhost:3000/#/about
特点

  • URL 格式带有 #,视觉上相对"丑陋";
  • 兼容性极强,支持所有主流浏览器,包括低版本 IE,因为锚点是早期 HTML 就支持的特性;
  • 无需后端配置,因为锚点部分不会被发送到服务器,后端无需对路由路径做额外处理。

1.2.2 BrowserRouter:基于 HTML5 History API 的路由

BrowserRouter 采用 HTML5 新增的 History API(pushStatereplaceState 等方法)实现路由控制,URL 格式与后端路由一致,不包含锚点,例如 http://localhost:3000/about。History API 允许前端直接操作浏览器的历史记录栈,实现 URL 变化而不刷新页面。
特点

  • URL 格式简洁美观,与传统后端路由一致;
  • 兼容性稍弱,不支持 IE11 及以下版本,但其实现的功能更符合现代前端开发需求,且目前主流浏览器(Chrome、Firefox、Edge 等)均已完美支持;
  • 需要后端配合配置:当用户直接访问非根路径(如 http://localhost:3000/about)时,后端需将请求转发到根页面(index.html),否则会返回 404 错误(因为后端不存在该路由路径)。

1.3 路由别名:提升代码可读性

在实际开发中,为了简化代码并提升可读性,通常会为路由组件设置别名,使用 as 关键字实现。例如将 BrowserRouter 别名为 Router,避免在后续代码中重复书写冗长的组件名:

复制代码
import { BrowserRouter as Router } from 'react-router-dom';

这样的写法不仅简洁,还能让其他开发者快速理解代码意图,尤其在多人协作项目中,统一的别名规范能提升代码可维护性。

1.4 路由与性能优化:组件懒加载

单页应用的核心优势之一是加载速度快,但如果应用规模较大,一次性加载所有页面组件会导致初始加载体积过大,影响首屏渲染速度。react-router-dom 结合 React 提供的 lazySuspense 组件,实现路由级别的组件懒加载,有效优化性能。
懒加载核心逻辑:仅当用户访问某个路由时,才加载对应的组件,而非在应用初始化时全部加载。例如:

  • 用户访问根路径 / 时,仅加载 Home 组件,About 组件暂不加载;
  • 当用户跳转至 /about 路径时,再动态加载 About 组件。

这种方式能显著减小应用初始加载体积,提升首屏加载速度,是大型单页应用的必备优化手段。

二、react-router-dom 核心路由类型

react-router-dom 支持多种路由类型,覆盖不同业务场景,包括普通路由、动态路由、嵌套路由等,每种路由都有其特定的使用场景和实现方式。

2.1 普通路由:基础路径匹配

普通路由是最基础的路由类型,通过固定的 URL 路径匹配对应的组件,适用于页面路径固定的场景,例如首页、关于页、联系页等。其核心是 Route 组件,通过 path 属性指定路由路径,element 属性指定对应渲染的组件。

示例代码:

复制代码
import { Routes, Route } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';

function RouterConfig() {
  return (
    <Routes>
      <Route path="/" element={<Home />} /> {/* 根路径匹配 Home 组件 */}
      <Route path="/about" element={<About />} /> {/* /about 路径匹配 About 组件 */}
    </Routes>
  );
}

注意:Routes 组件用于包裹一组 Route 组件,相当于路由容器,确保路由规则有序匹配,每次仅渲染匹配到的第一个路由组件。

2.2 动态路由:路径参数传递

在实际业务中,很多页面路径并非固定,例如商品详情页、用户个人中心等,需要根据不同的 ID 展示不同内容。此时就需要使用动态路由,通过在路径中定义参数占位符(/:参数名),实现路径参数的传递与接收。

动态路由的路径格式通常为 /product/:id/user/:userId,其中 :id:userId 为参数占位符,代表可变的参数值。

2.2.1 动态路由定义

复制代码
<Routes>
  {/* 动态路由:匹配 /user/123、/user/456 等路径 */}
  <Route path="/user/:id" element={<UserProfile />} />
  {/* 商品详情动态路由:匹配 /products/789 等路径 */}
  <Route path="/products/:productId" element={<ProductDetail />} />
</Routes>

2.2.2 路径参数接收

在目标组件中,可通过 react-router-dom 提供的 useParams 钩子函数获取动态路由传递的参数。useParams 返回一个对象,键为参数占位符名称,值为 URL 中的实际参数值。

示例(UserProfile 组件):

复制代码
 import { useParams } from 'react-router-dom';

export default function UserProfile() {
  // 获取动态路由参数 id
  const { id } = useParams();
  return (
    <div>
      <h1>用户个人中心</h1>
      <p>用户 ID:{id}</p>
    </div>
  );
}

示例(ProductDetail 组件):

复制代码
import { useParams } from 'react-router-dom';

export default function ProductDetail() {
  // 获取商品 ID 参数
  const { productId } = useParams();
  return (
    <div>
      <h1>商品详情</h1>
      <p>商品 ID:{productId}</p>
    </div>
  );
}

注意 :动态路由参数仅能传递简单的字符串类型数据,若需传递复杂数据,可结合查询参数(Query String)或状态管理工具(如 Redux)实现。

2.3 通配路由:404 页面匹配

通配路由使用 * 作为路径匹配规则,可匹配所有未被前面路由规则匹配到的路径,主要用于实现 404 页面(页面不存在提示)。
核心规则 :通配路由必须放在所有路由规则的最后,因为路由匹配遵循"自上而下"的顺序,若放在前面,会优先匹配所有路径,导致其他路由失效。

示例代码:

复制代码
import NotFound from '../pages/NotFound';

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  {/* 通配路由:匹配所有未被匹配的路径,渲染 404 组件 */}
  <Route path="*" element={<NotFound />} />
</Routes>

404 组件可结合 useNavigate 钩子实现自动跳转功能,例如 6 秒后自动返回首页:

复制代码
import { useNavigate, useEffect } from 'react-router-dom';

const NotFound = () => {
  const navigate = useNavigate();
  useEffect(() => {
    // 6 秒后自动跳转到首页
    const timer = setTimeout(() => {
      navigate('/');
    }, 6000);
    // 清除定时器,避免内存泄漏
    return () => clearTimeout(timer);
  }, [navigate]);

  return (
    <div>
      <h1>404 Not Found</h1>
      <p>页面不存在,6 秒后自动返回首页...</p>
    </div>
  );
};

export default NotFound;

2.4 嵌套路由:页面结构复用

在复杂应用中,页面通常存在公共结构(如侧边栏、导航栏、页脚),嵌套路由可实现公共结构的复用,同时在公共结构中渲染不同的子路由内容。react-router-dom 中,嵌套路由通过 Outlet 组件实现子路由内容的渲染。

2.4.1 嵌套路由定义

嵌套路由的核心是在父路由中通过 children 属性定义子路由,父路由组件中通过 Outlet 组件指定子路由内容的渲染位置。

示例(产品模块嵌套路由):

复制代码
import { Routes, Route, Outlet } from 'react-router-dom';
import Product from '../pages/Product';
import ProductDetail from '../pages/Product/ProductDetail';
import NewProduct from '../pages/Product/NewProduct';

function RouterConfig() {
  return (
    <Routes>
      {/* 父路由:产品列表页 */}
      <Route path="/products" element={<Product />}>
        {/* 子路由:商品详情页,路径为 /products/:productId */}
        <Route path=":productId" element={<ProductDetail />} />
        {/* 子路由:新增商品页,路径为 /products/new */}
        <Route path="new" element={<NewProduct />} />
      </Route>
    </Routes>
  );
}

注意 :子路由的 path 属性无需添加前缀 /,否则会被解析为绝对路径,脱离父路由的嵌套关系。例如子路由 path="new" 对应绝对路径 /products/new,若写为 path="/new" 则对应绝对路径 /new

2.4.2 Outlet 组件使用
Outlet 是 react-router-dom 提供的内置组件,用于在父路由组件中预留子路由内容的渲染位置。当用户访问子路由路径时,对应的子路由组件会自动渲染到 Outlet 所在位置。

示例(Product 父组件):

复制代码
 import { Outlet } from 'react-router-dom';

export default function Product() {
  return (
    <div>
      <h1>产品列表</h1>
      <div className="product-container">
        {/* 侧边栏:公共结构 */}
        <aside>
          <ul>
            <li>商品分类 1</li>
            <li>商品分类 2</li>
          </ul>
        </aside>
        {/* 子路由内容渲染位置 */}
        <main><Outlet /></main>
      </div>
    </div>
  );
}

当用户访问 /products/123 时,Product 组件的侧边栏会保持不变,main 区域会渲染 ProductDetail 组件;访问 /products/new 时,main 区域会渲染 NewProduct 组件,实现公共结构复用与子内容动态切换。

2.5 鉴权路由:路由守卫实现

在实际应用中,部分页面需要用户登录后才能访问(如个人中心、支付页面),鉴权路由(也称路由守卫)用于控制路由的访问权限,未登录用户访问时会自动跳转到登录页。react-router-dom 中可通过自定义 ProtectRoute 组件实现鉴权逻辑。

2.5.1 鉴权组件实现

自定义 ProtectRoute 组件,通过 children 属性接收需要鉴权的组件,判断用户登录状态(可通过 localStoragesessionStorage 或状态管理工具存储登录状态),未登录则通过 Navigate 组件跳转到登录页,已登录则渲染目标组件。

复制代码
import { Navigate } from 'react-router-dom';

// 鉴权路由组件
export default function ProtectRoute({ children }) {
  // 从 localStorage 获取登录状态(登录成功时设置 localStorage.setItem('isLogin', 'true'))
  const isLoggedIn = localStorage.getItem('isLogin') === 'true';
  // 未登录:跳转到登录页
  if (!isLoggedIn) {
    return <Navigate to="/login" />;
  }
  // 已登录:渲染目标组件
  return <div>{children}</div>;
}

2.5.2 鉴权路由使用

在路由配置中,将需要鉴权的路由组件用 ProtectRoute 包裹,即可实现权限控制。

复制代码
import ProtectRoute from '../components/ProtectRoute';
import Pay from '../pages/Pay';
import Login from '../pages/Login';

<Routes>
  <Route path="/login" element={<Login />} />
  {/* 支付页面需要鉴权 */}
  <Route path="/pay" element={
    <ProtectRoute>
      <Pay />
    </ProtectRoute>
  } />
</Routes>

扩展 :鉴权逻辑可根据业务需求升级,例如区分普通用户与管理员权限,不同角色展示不同路由;或结合接口请求验证 Token 有效性,实现更严谨的权限控制。

2.6 重定向路由:路径跳转

重定向路由用于将一个路径自动跳转到另一个路径,例如旧路径废弃后,将用户访问旧路径时重定向到新路径。react-router-dom v6 中,Redirect 组件已被 Navigate 组件替代,Navigate 组件通过 to 属性指定目标路径,replace 属性控制跳转方式。

2.6.1 基础重定向

示例:将 /old-path 重定向到 /new-path

import { Navigate } from 'react-router-dom'; <Routes> {/* 重定向:访问 /old-path 自动跳转到 /new-path */} <Route path="/old-path" element={<Navigate to="/new-path" />} /> <Route path="/new-path" element={<NewPath />} /> </Routes>

2.6.2 replace 跳转模式
Navigate 组件默认使用 push 模式跳转,会在浏览器历史记录栈中添加新记录,用户点击后退按钮可返回上一页;若添加 replace 属性,则使用 replace 模式跳转,会替换当前历史记录栈中的内容,不会留下跳转痕迹,用户点击后退按钮无法返回上一页。

复制代码
// replace 模式重定向,替换当前历史记录
<Route path="/old-path" element={<Navigate replace to="/new-path" />} />

使用场景 :登录页跳转至首页时,通常使用 replace 模式,避免用户点击后退按钮重新回到登录页;普通页面跳转则使用默认的 push 模式。

三、路由历史记录与跳转控制

3.1 历史记录栈结构

浏览器的历史记录采用 栈结构 存储,遵循"先进后出"的原则。当用户通过路由跳转时,本质上是对历史记录栈进行操作:

  • push 跳转:向栈中添加一条新的历史记录,栈长度加 1;
  • replace 跳转:替换栈顶的历史记录,栈长度不变;
  • 后退操作:弹出栈顶的历史记录,栈长度减 1,页面回到上一个路径。

react-router-dom 中的 Navigate 组件、useNavigate 钩子均基于此栈结构实现跳转控制。

3.2 useNavigate 钩子:编程式跳转

除了通过 Link 组件实现声明式跳转,react-router-dom 还提供 useNavigate 钩子,用于在组件逻辑中实现编程式跳转(如按钮点击后跳转、接口请求成功后跳转等)。

3.2.1 基础用法

复制代码
import { useNavigate } from 'react-router-dom';

function Login() {
  const navigate = useNavigate();

  const handleLogin = () => {
    // 模拟登录接口请求成功
    const loginSuccess = true;
    if (loginSuccess) {
      // 存储登录状态
      localStorage.setItem('isLogin', 'true');
      // 跳转到首页(push 模式)
      navigate('/');
      // 若使用 replace 模式跳转:navigate('/', { replace: true });
    }
  };

  return (
    <div>
      <h1>登录页</h1>
      <button onClick={handleLogin}>登录</button>
    </div>
  );
}

3.2.2 后退与前进操作
useNavigate 还支持通过传递数字参数实现后退、前进操作,正数表示前进,负数表示后退:

复制代码
// 后退一页(相当于浏览器的后退按钮)
navigate(-1);
// 前进一页(相当于浏览器的前进按钮)
navigate(1);
// 后退两页
navigate(-2);

四、单页应用与路由集成实战

4.1 单页应用(SPA)的核心优势

单页应用(Single Page Application,SPA)是基于前端路由实现的一种应用架构,其核心特点是整个应用仅加载一个 HTML 页面,后续页面切换均通过前端路由控制,无需重新请求服务器。
与传统多页应用的对比

  • 传统多页应用:每次 URL 变化都会向服务器发送 HTTP 请求,加载完整 HTML 页面,页面会出现白屏、加载动画,用户体验较差;
  • 单页应用:仅初始加载一次 HTML、CSS、JS 资源,后续路由变化时,前端通过捕获 URL 变化事件,匹配对应的组件并渲染,无页面刷新,加载速度快,用户体验流畅。

react-router-dom 是构建 React 单页应用的核心工具,结合 HTML5 History API 实现前端路由控制,完美支撑单页应用的页面切换需求。

4.2 完整路由集成案例

以下结合前文知识点,实现一个完整的 React 单页应用路由集成案例,包含路由配置、组件懒加载、导航栏、鉴权控制、加载动画等功能。

4.2.1 项目结构

复制代码
src/
├── components/       // 公共组件
│   ├── Navigation.js // 导航栏组件
│   ├── ProtectRoute.js // 鉴权路由组件
│   └── LoadingFallback.js // 加载动画组件
├── pages/            // 页面组件
│   ├── Home.js       // 首页
│   ├── About.js      // 关于页
│   ├── Login.js      // 登录页
│   ├── Pay.js        // 支付页(需鉴权)
│   ├── NotFound.js   // 404 页面
│   └── Product/      // 产品模块
│       ├── Product.js // 产品列表页(父路由)
│       ├── ProductDetail.js // 商品详情页(子路由)
│       └── NewProduct.js // 新增商品页(子路由)
├── router/           // 路由配置
│   └── index.js      // 路由配置文件
├── App.js            // 根组件
└── index.js          // 入口文件

4.2.2 加载动画组件(LoadingFallback.js)

用于懒加载组件加载过程中的占位展示,结合 CSS 动画实现旋转加载效果:

复制代码
import styles from './index.module.css';

export default function LoadingFallback() {
  return (
    <div className={styles.container}>
      <div className={styles.spinner}>
        <div className={styles.circle}></div>
        <div className={`${styles.circle} ${styles.inner}`}></div>
      </div>
      <p className={styles.text}>Loading...</p>
    </div>
  );
}

对应的 CSS 样式(index.module.css):

复制代码
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background-color: rgba(255, 255, 255, 0.9);
}

.spinner {
  position: relative;
  width: 60px;
  height: 60px;
}

.circle {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 4px solid transparent;
  border-top-color: #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.circle.inner {
  width: 70%;
  height: 70%;
  top: 15%;
  left: 15%;
  border-top-color: #e74c3c;
  animation: spin 0.8s linear infinite reverse;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.text {
  margin-top: 20px;
  color: #2c3e50;
  font-size: 18px;
  font-weight: 500;
  animation: pulse 1s ease-in-out infinite;
}

@keyframes pulse {
  0% { opacity: 0.6; }
  50% { opacity: 1; }
  100% { opacity: 0.6; }
}

4.2.3 导航栏组件(Navigation.js)

实现页面导航功能,通过 Link 组件实现声明式跳转,并通过 useResolvedPathuseMatch 钩子实现导航高亮效果:

复制代码
import { Link, useResolvedPath, useMatch } from 'react-router-dom';

export default function Navigation() {
  // 导航高亮逻辑
  const isActive = (to) => {
    const resolvedPath = useResolvedPath(to);
    const match = useMatch({
      path: resolvedPath.pathname,
      end: true, // 完全匹配路径
    });
    return match ? 'active' : '';
  };

  return (
    <nav style={{ background: '#f5f5f5', padding: '10px' }}>
      <ul style={{ listStyle: 'none', display: 'flex', gap: '20px', margin: 0, padding: 0 }}>
        <li>
          <Link to="/" className={isActive('/')} style={{ textDecoration: 'none' }}>
            首页
          </Link>
        </li>
        <li>
          <Link to="/about" className={isActive('/about')} style={{ textDecoration: 'none' }}>
            关于我们
          </Link>
        </li>
        <li>
          <Link to="/products" className={isActive('/products')} style={{ textDecoration: 'none' }}>
            产品列表
          </Link>
        </li>
        <li>
          <Link to="/products/new" className={isActive('/products/new')} style={{ textDecoration: 'none' }}>
            新增商品
          </Link>
        </li>
        <li>
          <Link to="/pay" className={isActive('/pay')} style={{ textDecoration: 'none' }}>
            支付中心
          </Link>
        </li>
        <li>
          <Link to="/old-path" className={isActive('/old-path')} style={{ textDecoration: 'none' }}>
            旧路径(测试重定向)
          </Link>
        </li>
      </ul>
    </nav>
  );
}

说明end: true 表示完全匹配路径,例如 /products 不会匹配 /products/123,确保导航高亮的准确性;active 类名可结合 CSS 样式实现高亮效果(如改变文字颜色、添加下划线)。

4.2.4 路由配置文件(router/index.js)

结合组件懒加载、嵌套路由、鉴权路由、重定向等功能,统一配置所有路由规则:

复制代码
import { lazy, Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import LoadingFallback from '../components/LoadingFallback';
import ProtectRoute from '../components/ProtectRoute';

// 懒加载页面组件
const Home = lazy(() => import('../pages/Home'));
const About = lazy(() => import('../pages/About'));
const Login = lazy(() => import('../pages/Login'));
const Pay = lazy(() => import('../pages/Pay'));
const NotFound = lazy(() => import('../pages/NotFound'));
const Product = lazy(() => import('../pages/Product/Product'));
const ProductDetail = lazy(() => import('../pages/Product/ProductDetail'));
const NewProduct = lazy(() => import('../pages/Product/NewProduct'));
const NewPath = lazy(() => import('../pages/NewPath'));

export default function RouterConfig() {
  return (
    <Suspense fallback={<LoadingFallback />}>
      <Routes>
        {/* 普通路由 */}
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/login" element={<Login />} />

        {/* 动态路由 + 嵌套路由 */}
        <Route path="/products" element={<Product />}>
          <Route path=":productId" element={<ProductDetail />} />
          <Route path="new" element={<NewProduct />} />
        </Route>

        {/* 鉴权路由 */}
        <Route path="/pay" element={
          <ProtectRoute>
            <Pay />
          </ProtectRoute>
        } />

        {/* 重定向路由 */}
        <Route path="/old-path" element={<Navigate replace to="/new-path" />} />
        <Route path="/new-path" element={<NewPath />} />

        {/* 通配路由(404) */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Suspense>
  );
}

说明Suspense 组件包裹所有路由,fallback 属性指定懒加载组件加载时的占位内容(加载动画);路由规则按"普通路由 → 嵌套路由 → 鉴权路由 → 重定向路由 → 通配路由"的顺序排列,确保匹配逻辑正确。

4.2.5 根组件(App.js)

集成路由容器与导航栏,作为应用的入口组件:

复制代码
import { BrowserRouter as Router } from 'react-router-dom';
import Navigation from './components/Navigation';
import RouterConfig from './router';

export default function App() {
  return (
    <Router>
      {/* 导航栏:全局公共组件 */}
      <Navigation />
      {/* 路由配置 */}
      <RouterConfig />
    </Router>
  );
}

4.2.6 页面组件示例(Home.js、About.js)

复制代码
// Home.js
export default function Home() {
  console.log('首页组件加载');
  return (
    <div style={{ padding: '20px' }}>
      <h1>首页</h1>
      <p>欢迎访问 React 路由实战应用!</p>
    </div>
  );
}

 
// About.js
console.log('About 组件加载日志');
export default function About() {
  console.log('About 组件渲染');
  return (
    <div style={{ padding: '20px' }}>
      <h1>关于我们</h1>
      <p>这是一个基于 react-router-dom 构建的单页应用示例。</p>
    </div>
  );
}

4.3 实战注意事项

  • 路由匹配顺序:Routes 组件会自上而下匹配路由,通配路由必须放在最后,否则会覆盖其他路由;
  • 懒加载优化:仅对非首屏组件使用懒加载,首屏组件(如 Home)建议直接加载,避免首屏加载过慢;
  • 后端配置:使用 BrowserRouter 时,后端需配置路由转发,确保所有路径都指向 index.html,避免 404 错误;
  • 内存泄漏防范:使用 useEffect 实现自动跳转、定时器等功能时,需清除副作用(如定时器、事件监听);
  • 导航高亮:useMatch 钩子的 end 属性需根据需求合理设置,避免高亮错误。

五、react-router-dom 核心原理剖析

5.1 路由匹配原理

react-router-dom 的路由匹配核心是"路径与组件的映射关系",其流程如下:

  1. 用户改变 URL(通过点击 Link 组件、编程式跳转或手动输入 URL);
  2. 路由容器(Router)捕获 URL 变化事件(HashRouter 监听 hashchange 事件,BrowserRouter 监听 popstate 事件);
  3. Routes 组件遍历所有 Route 子组件,根据 path 属性匹配当前 URL 路径;
  4. 匹配成功后,渲染对应 Route 组件的 element 属性内容;若未匹配到任何路由,则渲染通配路由(若存在)。

5.2 历史记录管理原理

BrowserRouter 基于 HTML5 History API 实现历史记录管理,核心 API 包括:

  • history.pushState(state, title, url):向历史记录栈添加一条新记录,改变 URL 但不刷新页面;
  • history.replaceState(state, title, url):替换当前历史记录栈顶内容,改变 URL 但不刷新页面;
  • popstate 事件:当用户点击后退、前进按钮或调用 history.back()history.forward() 时触发,路由容器通过监听该事件更新组件渲染。

HashRouter 则通过监听 hashchange 事件捕获锚点变化,无需依赖 History API,兼容性更强,但 URL 格式不够美观。

5.3 嵌套路由实现原理

嵌套路由的核心是 Outlet 组件,其原理的本质是"父子路由的路径拼接与内容分发":

  1. 父路由的 path 与子路由的 path 自动拼接,形成完整的 URL 路径(如父路由 /products + 子路由 :productId/products/:productId);
  2. 当用户访问子路由路径时,路由系统同时匹配父路由与子路由;
  3. 父路由组件渲染时,Outlet 组件会接收路由系统传递的子路由组件,将其渲染到指定位置,实现父子路由内容的联动展示。

六、常见问题与解决方案

6.1 路由跳转后页面不刷新
问题原因 :单页应用路由跳转本质是组件切换,而非页面刷新,若组件依赖 URL 参数或状态更新,可能因未重新获取数据导致页面内容未更新。
解决方案

  • 使用 useEffect 监听 URL 参数变化,参数变化时重新请求数据:

    import { useParams, useEffect } from 'react-router-dom';

    function ProductDetail() {
    const { productId } = useParams();
    useEffect(() => {
    // 监听 productId 变化,重新请求商品详情数据
    fetchProductDetail(productId);
    }, [productId]);
    // ...
    }

6.2 BrowserRouter 部署后刷新 404
问题原因 :BrowserRouter 的 URL 路径与后端路由冲突,用户直接访问非根路径时,后端无法匹配该路径,返回 404 错误。
解决方案

  • Nginx 配置:在 Nginx 配置文件中添加路由转发规则,将所有请求转发到 index.html:

    location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files uri uri/ /index.html; # 路由转发
    }
    root /usr/share/nginx/html; index index.html index.htm; try_files uri uri/ /index.html; # 路由转发 }

  • Apache 配置:修改 .htaccess 文件,添加重写规则;

  • 开发环境:使用 create-react-app 时,开发服务器已默认配置转发,无需额外处理。

6.3 懒加载组件加载失败
问题原因 :组件路径错误、网络异常或 Suspense 组件使用不当。
解决方案

  • 检查懒加载组件的导入路径是否正确,确保路径与文件位置一致;
  • 确保 Suspense 组件包裹懒加载组件,且 fallback 属性设置有效;
  • 添加错误边界组件(Error Boundary),捕获懒加载失败错误,提升用户体验。

6.4 导航高亮不准确
问题原因useMatch 钩子的 end 属性设置不当,或路由路径重叠。
解决方案

  • 精确匹配路径时设置 end: true,模糊匹配时省略该属性;
  • 避免路由路径重叠,例如 /products/products/new 需确保父路由不设置 end: true,子路由正常匹配。

七、总结与扩展

7.1 核心知识点总结

react-router-dom 是 React 单页应用的核心路由库,其核心知识点可概括为:

  • 两种路由模式:HashRouter(兼容性强)与 BrowserRouter(URL 美观,需后端配置);
  • 六大路由类型:普通路由、动态路由、通配路由、嵌套路由、鉴权路由、重定向路由;
  • 核心 API 与钩子:RoutesRouteLinkNavigateOutletuseParamsuseNavigateuseMatch 等;
  • 性能优化:组件懒加载(lazy + Suspense);
  • 实战要点:路由匹配顺序、后端配置、内存泄漏防范、导航高亮控制。

7.2 扩展学习方向

掌握基础用法后,可进一步学习以下内容,提升路由使用能力:

  • 路由状态管理:结合 Redux、React Context 实现路由状态全局共享;
  • 高级鉴权逻辑:基于角色的访问控制(RBAC)、Token 过期自动跳转;
  • 路由动画:结合 React Transition Group 实现页面切换动画;
  • 多语言路由:实现国际化路由(如 /en/about/zh/about);
  • react-router-dom v6 新特性:对比 v5 版本的差异(如 Routes 替代 SwitchNavigate 替代 Redirect 等)。

react-router-dom 是 React 开发的必备技能之一,熟练掌握其用法与原理,能有效提升单页应用的开发效率与用户体验。在实际开发中,需结合业务场景灵活选择路由模式与实现方案,不断优化路由配置与性能,构建高质量的 React 应用。

原文: https://juejin.cn/post/75962419

相关推荐
宵时待雨2 小时前
数据结构(初阶)笔记归纳5:单链表的应用
c语言·开发语言·数据结构·笔记·算法
saoys2 小时前
Opencv 学习笔记:直方图均衡化(灰度 / 彩色图像二值化优化)
笔记·opencv·学习
嗯嗯=2 小时前
STM32单片机学习篇2
stm32·单片机·学习
炽烈小老头2 小时前
【 每天学习一点算法 2026/01/19】位1的个数
学习·算法
弓.长.2 小时前
基础入门 React Native 鸿蒙跨平台开发:LayoutAnimation 布局动画
react native·react.js·harmonyos
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——迭代器模式
笔记·设计模式
夜流冰2 小时前
Git - 学习掌握Git的建议
git·学习
摘星编程2 小时前
React Native for OpenHarmony 实战:WebView 网页视图组件
react native·react.js·ios
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习:GetX 全家桶:从状态管理到路由导航的极简艺术
学习·flutter·ui·华为·harmonyos·鸿蒙