React Router 全家桶实战:从路由懒加载到嵌套路由的 6 大核心玩法

React Router 全家桶实战:从路由懒加载到嵌套路由的 6 大核心玩法

SPA 单页应用的核心是前端路由,而 React 生态中最成熟的路由方案就是 react-router-dom。本文从 RESTful 资源理念出发,带你完整掌握 React Router 的 基本路由配置、路由懒加载、动态路由参数、嵌套路由、路由重定向、404 兜底 六大核心玩法。全文代码可直接运行,建议收藏后动手实践。


一、从 RESTful 理念理解前端路由

1.1 一切皆资源

RESTful 的核心思想是一切皆资源,每个 URL 对应一个唯一的资源:

bash 复制代码
GET  /users          → 用户列表资源
GET  /users/123      → 用户 123 的详情资源
POST /users          → 创建用户资源
GET  /products       → 商品列表资源
GET  /products/45    → 商品 45 的详情资源

前端路由沿用了这个理念------每个 URL 对应一个页面(资源)

bash 复制代码
/                   → 首页
/about              → 关于页面
/user/:id           → 用户详情页
/products           → 商品列表页
/products/:id       → 商品详情页
/products/new       → 新建商品页

1.2 前端路由 vs 后端路由

bash 复制代码
后端路由(传统多页应用):
  浏览器请求 /about
      │
      ▼
  服务器返回 about.html
      │
      ▼
  浏览器整页重新渲染(白屏)

前端路由(SPA 单页应用):
  浏览器请求 /  (只加载一次 index.html)
      │
      ▼
  用户点击 /about 链接
      │
      ▼
  hash 变化 → hashchange 事件
      │
      ▼
  JS 监听到变化,替换 #container 内容(无白屏)
维度 后端路由 前端路由
页面加载 整页重新加载 局部 DOM 替换
用户体验 白屏闪烁 丝滑流畅
服务器负担 每次返回完整 HTML 首次加载后不再请求 HTML
适用场景 传统网站 现代 SPA 应用

1.3 React 全家桶中的路由定位

React 开发"全家桶"由三部分组成:

复制代码
┌─────────────────────────────────────────────┐
│            React 全家桶                      │
│                                              │
│  ┌─────────────┐  UI 界层                    │
│  │   React      │  组件开发、响应式、Hooks    │
│  └─────────────┘                            │
│                                              │
│  ┌─────────────┐  路由层                     │
│  │ react-router │  SPA 页面切换、路由管理     │
│  │   -dom       │                            │
│  └─────────────┘                            │
│                                              │
│  ┌─────────────┐  状态层                     │
│  │  Zustand /   │  全局状态管理               │
│  │  Pinia       │                            │
│  └─────────────┘                            │
└─────────────────────────────────────────────┘

二、环境准备与项目结构

2.1 安装依赖

bash 复制代码
npm install react-router-dom

2.2 项目结构

bash 复制代码
src/
├── App.jsx                 # 路由配置入口
├── components/
│   └── Navigation.jsx      # 导航栏组件
├── pages/
│   ├── Home.jsx            # 首页
│   ├── About.jsx           # 关于页面
│   ├── UserProfile.jsx     # 用户详情页(动态路由)
│   ├── Products/
│   │   ├── index.jsx       # 商品列表页(嵌套路由父级)
│   │   ├── Detail.jsx      # 商品详情页(二级路由)
│   │   └── New.jsx         # 新建商品页(二级路由)
│   └── NotFound.jsx        # 404 页面

三、六大核心玩法完整实现

3.1 完整路由配置代码

jsx 复制代码
import { lazy, Suspense } from 'react';
import {
  HashRouter as Router,    // 基于 hashchange 的前端路由
  Routes,                   // 路由配置容器
  Route,                    // 单个路由配置项
  Navigate                  // 路由重定向组件
} from 'react-router-dom';
import Navigation from './components/Navigation';

// ===== 路由懒加载:按需加载页面组件 =====
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const UserProfile = lazy(() => import('./pages/UserProfile'));
const NotFound = lazy(() => import('./pages/NotFound'));
const Products = lazy(() => import('./pages/Products'));
const ProductDetail = lazy(() => import('./pages/Products/Detail'));
const NewProduct = lazy(() => import('./pages/Products/New'));

const App = () => {
  return (
    <>
      {/* Router 接管整个应用的前端路由 */}
      <Router>
        {/* Suspense 包裹懒加载组件,加载期间显示 fallback */}
        <Suspense fallback={<div>Loading...</div>}>
          <Navigation />

          {/* 路由出口:当前 URL 对应的页面组件会渲染到这里 */}
          <div id="container">
            <Routes>
              {/* ① 基本路由:精确匹配 path */}
              <Route path="/" element={<Home />} />
              <Route path="/about" element={<About />} />

              {/* ② 动态路由::id 是路由参数 */}
              <Route path="/user/:id" element={<UserProfile />} />

              {/* ③ 嵌套路由:父路由 + 子路由 */}
              <Route path="/products" element={<Products />}>
                <Route path=":productId" element={<ProductDetail />} />
                <Route path="new" element={<NewProduct />} />
              </Route>

              {/* ④ 路由重定向:旧路径跳转到新路径 */}
              <Route path="/old-path" element={<Navigate replace to="/about" />} />

              {/* ⑤ 404 兜底:* 通配所有未匹配的路由 */}
              <Route path="*" element={<NotFound />} />
            </Routes>
          </div>
        </Suspense>
      </Router>
    </>
  );
};

export default App;

四、核心玩法逐一深入

4.1 玩法一:基本路由配置

jsx 复制代码
<Router>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
  </Routes>
</Router>

三个核心组件的职责:

组件 职责 类比
Router 路由容器,接管整个应用 交通指挥中心
Routes 路由配置容器,决定哪个 Route 显示 路线图
Route 单个路由配置项,URL → 组件的映射 一条路线

工作原理:

arduino 复制代码
URL 变化(hashchange)
    │
    ▼
Router 监听到变化
    │
    ▼
Routes 遍历所有 Route
    │
    ▼
找到 path 匹配的 Route
    │
    ▼
渲染对应 element 组件到 #container

关键规则:<Routes> 中有且只有一个 <Route> 会被渲染------就是当前 URL 匹配的那一个。

4.2 玩法二:路由懒加载

jsx 复制代码
// ❌ 传统导入:所有页面一次性加载
import Home from './pages/Home';
import About from './pages/About';
// 首页加载时就把所有页面代码都下载了,慢!

// ✅ 懒加载导入:按需加载
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
// 只有访问对应路由时才下载该页面的代码

// 必须配合 Suspense 使用
<Suspense fallback={<div>Loading...</div>}>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
  </Routes>
</Suspense>

为什么需要懒加载?

bash 复制代码
不使用懒加载:
┌──────────────────────────────────────┐
│  首次加载 index.html                  │
│  下载:Home + About + UserProfile    │
│        + Products + NotFound + ...   │
│  打包体积:800KB                      │
│  首屏加载时间:3.2s                   │
└──────────────────────────────────────┘

使用懒加载:
┌──────────────────────────────────────┐
│  首次加载 index.html                  │
│  下载:Home + Navigation             │
│  打包体积:120KB                      │
│  首屏加载时间:0.8s                   │
│                                       │
│  访问 /about 时才下载 About.js       │
│  访问 /products 时才下载 Products.js │
└──────────────────────────────────────┘

lazy + Suspense 工作流程:

xml 复制代码
用户访问 /about
    │
    ▼
React 发现 About 组件是 lazy 导入
    │
    ▼
React 发起网络请求下载 About.js
    │
    ▼
下载期间:显示 <Suspense fallback={<div>Loading...</div>} />
    │
    ▼
下载完成:渲染 <About /> 组件

4.3 玩法三:动态路由参数

jsx 复制代码
// 路由配置
<Route path="/user/:id" element={<UserProfile />} />

// UserProfile 组件中获取参数
import { useParams } from 'react-router-dom';

const UserProfile = () => {
  const { id } = useParams();  // 从 URL 中提取 id 参数
  return <h1>用户 {id} 的个人主页</h1>;
};

// URL: /user/123    → id = "123"
// URL: /user/abc    → id = "abc"

动态路由匹配规则:

bash 复制代码
路由模式              URL              匹配结果
/user/:id           /user/123         ✅ id = "123"
/user/:id           /user/abc         ✅ id = "abc"
/user/:id           /user/            ❌ 缺少参数
/user/:id           /user/123/posts   ❌ 多余路径
/user/:id/:tab      /user/123/posts   ✅ id="123", tab="posts"

4.4 玩法四:嵌套路由

jsx 复制代码
// 父路由配置
<Route path="/products" element={<Products />}>
  {/* 子路由使用相对路径 */}
  <Route path=":productId" element={<ProductDetail />} />
  <Route path="new" element={<NewProduct />} />
</Route>

// 父组件 Products 中必须使用 <Outlet /> 渲染子路由
import { Outlet, Link } from 'react-router-dom';

const Products = () => {
  return (
    <div>
      <h1>商品管理</h1>
      <nav>
        <Link to="new">新建商品</Link>
        <Link to="123">商品详情</Link>
      </nav>
      {/* Outlet 是子路由的渲染出口 */}
      <Outlet />
    </div>
  );
};

嵌套路由的 URL 对应关系:

xml 复制代码
URL                    父组件渲染        子组件渲染
/products              <Products />      ❌ 无子路由匹配
/products/123          <Products />      <ProductDetail />  (productId="123")
/products/new          <Products />      <NewProduct />

嵌套路由渲染结构:

xml 复制代码
<div id="container">
  ┌─────────────────────────────────┐
  │  <Products />  (父路由组件)      │
  │  ┌───────────────────────────┐  │
  │  │  <h1>商品管理</h1>         │  │
  │  │  <nav>导航链接</nav>       │  │
  │  │  ┌─────────────────────┐  │  │
  │  │  │  <Outlet />         │  │  │
  │  │  │  子路由组件渲染在这里 │  │  │
  │  │  │  <ProductDetail />  │  │  │
  │  │  └─────────────────────┘  │  │
  │  └───────────────────────────┘  │
  └─────────────────────────────────┘

4.5 玩法五:路由重定向

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

// 旧路径重定向到新路径
<Route path="/old-path" element={<Navigate replace to="/about" />} />

// 用户访问 /old-path 时:
// 1. 自动跳转到 /about
// 2. replace 表示替换浏览历史(用户点后退不会回到 /old-path)

replace vs push 的区别:

bash 复制代码
push(默认):
  浏览历史:[/] → [/old-path] → [/about]
  用户点后退:从 /about 回到 /old-path → 又重定向到 /about
  ❌ 死循环

replace:
  浏览历史:[/] → [/about]
  用户点后退:从 /about 回到 /
  ✅ 正常
场景 选择 原因
旧路径迁移 replace 避免后退死循环
登录后跳转 replace 不需要回到登录页
页面间导航 push(默认) 保留浏览历史

4.6 玩法六:404 兜底

jsx 复制代码
// * 通配所有未匹配的路由,必须放在最后
<Route path="*" element={<NotFound />} />

// NotFound 组件
import { Link } from 'react-router-dom';

const NotFound = () => {
  return (
    <div>
      <h1>404 - 页面不存在</h1>
      <p>你访问的页面不存在</p>
      <Link to="/">返回首页</Link>
    </div>
  );
};

匹配优先级:

ini 复制代码
Routes 会从上到下匹配,命中一个就停止

<Route path="/" />              ← 精确匹配 /
<Route path="/about" />         ← 精确匹配 /about
<Route path="/user/:id" />      ← 匹配 /user/任意值
<Route path="/products">        ← 匹配 /products 及其子路由
<Route path="/old-path" />      ← 精确匹配 /old-path
<Route path="*" />              ← 兜底:匹配以上都不命中的所有 URL

所以 * 必须放在最后!

五、导航组件的实现

jsx 复制代码
// components/Navigation.jsx
import { Link, NavLink } from 'react-router-dom';

const Navigation = () => {
  return (
    <header>
      <nav>
        <ul>
          {/* Link:普通导航链接 */}
          <li><Link to="/">首页</Link></li>
          <li><Link to="/about">关于</Link></li>
          <li><Link to="/user/123">用户 123</Link></li>
          <li><Link to="/products">商品管理</Link></li>

          {/* NavLink:带 active 状态的导航链接 */}
          <li>
            <NavLink
              to="/about"
              className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
            >
              关于(高亮当前)
            </NavLink>
          </li>
        </ul>
      </nav>
    </header>
  );
};

export default Navigation;

Link vs NavLink:

组件 特点 适用场景
Link 简单导航,无状态 普通跳转链接
NavLink 自动添加 active 状态 导航栏高亮当前页

六、HashRouter vs BrowserRouter

jsx 复制代码
// HashRouter:URL 带 # 号
import { HashRouter as Router } from 'react-router-dom';
// URL: https://example.com/#/about

// BrowserRouter:URL 无 # 号
import { BrowserRouter as Router } from 'react-router-dom';
// URL: https://example.com/about
对比 HashRouter BrowserRouter
URL 形态 /#/about /about
美观度 ❌ 有 # 号 ✅ 干净
服务器配置 不需要 需要 fallback 到 index.html
原理 hashchange 事件 History API (pushState)
兼容性 IE8+ IE10+
部署难度 简单 需配置 nginx

BrowserRouter 服务器配置示例(nginx):

nginx 复制代码
location / {
  try_files $uri $uri/ /index.html;
}

七、六大玩法全景总结

7.1 路由配置速查表

jsx 复制代码
<Router>
  <Suspense fallback={<div>Loading...</div>}>
    <Routes>
      {/* ① 基本路由 */}
      <Route path="/" element={<Home />} />

      {/* ② 动态路由 */}
      <Route path="/user/:id" element={<UserProfile />} />

      {/* ③ 嵌套路由 */}
      <Route path="/products" element={<Products />}>
        <Route path=":productId" element={<ProductDetail />} />
        <Route path="new" element={<NewProduct />} />
      </Route>

      {/* ④ 重定向 */}
      <Route path="/old-path" element={<Navigate replace to="/about" />} />

      {/* ⑤ 404 兜底 */}
      <Route path="*" element={<NotFound />} />
    </Routes>
  </Suspense>
</Router>

7.2 核心知识点对照表

知识点 API 作用
路由容器 HashRouter / BrowserRouter 接管应用路由
路由配置 Routes + Route URL → 组件映射
懒加载 lazy + Suspense 按需加载页面,优化首屏
动态参数 :id + useParams() URL 中提取参数
嵌套路由 Route + 子 Route + Outlet 多级路由结构
重定向 Navigate 旧路径跳转新路径
404 兜底 path="*" 匹配所有未定义路由
导航链接 Link / NavLink 声明式导航

7.3 数据流全景图

xml 复制代码
用户点击 <Link to="/products/123">
    │
    ▼
Router 监听到 URL 变化(hashchange / popstate)
    │
    ▼
Routes 遍历路由表,匹配 /products/:productId
    │
    ▼
渲染 <Products />(父路由组件)
    │
    ▼
<Products /> 内部的 <Outlet /> 渲染 <ProductDetail />
    │
    ▼
如果组件是 lazy 加载,先显示 <Suspense fallback>
    │
    ▼
下载完成后渲染实际组件

八、总结

8.1 React Router 的设计哲学

markdown 复制代码
1. 一切皆组件
   Router、Routes、Route、Link、Navigate 都是组件
   用 JSX 声明式配置路由

2. 路由即状态
   URL 是应用状态的映射
   路由变化 = 状态变化 = UI 更新

3. 按需加载
   lazy + Suspense 实现代码分割
   只加载当前需要的页面

4. 组合优于继承
   嵌套路由通过组件组合实现
   <Outlet /> 是子路由的渲染出口

8.2 路由类型速查

路由类型 语法 示例 URL 用途
精确路由 path="/about" /about 固定页面
动态路由 path="/user/:id" /user/123 带参数的页面
嵌套路由 父+子 Route /products/123 多级页面结构
重定向 Navigate to=... /old/new 路径迁移
通配路由 path="*" 任意未匹配 URL 404 兜底

如果这篇文章对你有帮助,欢迎点赞收藏

相关推荐
阿黎梨梨1 小时前
React 路由是怎么“瞒”过浏览器实现无刷新跳转的?
react.js
Goodbye1 小时前
组件详解:从起源到未来的全方位解读
前端
无糖可可果1 小时前
从前端路由的起源到 React Router 实战
前端
亿元程序员1 小时前
竹知了很火?于是我用Cocos做了一个
前端
八号当铺1 小时前
我做了一个多端基金收益助手:从养基宝数据到 Web、桌面端、浏览器插件和 IDE 插件
前端·人工智能·github
用户852495071841 小时前
从多页到 SPA:用 50 行代码理解前端路由的本质
前端
Zldaisy3d1 小时前
连续纤维增材制造的机翼已飞上天,复材打印在低空飞行器上还需翻过几道坎?
java·前端·数据库
__sjfzllv___1 小时前
在职前端Leader学习/转行 AI Agent -DAY24
前端
喜欢睡觉1 小时前
从"白一下"到"丝滑切换"——前端路由的进化史
前端