Skip to content

Lesson 07:多页面架构 — React Router v7 基础

🧩 本节信息卡(学习前先看)

  • 阶段定位:Phase 2(进阶篇)
  • 推荐时长:75~120 分钟(首次学习)
  • 先修要求:完成 Phase 1,理解组件拆分与基础状态管理
  • 学习产出:完成本节功能,并能用自己的话解释关键设计取舍
✅ 本节完成标准(自检清单)
  • [ ] 我可以独立复现文中的核心代码片段
  • [ ] 我能解释“为什么这样实现”,而不只是“照着写”
  • [ ] 我记录了至少 1 个踩坑点和修复方法

🧭 本节统一学习流程

  1. 学习目标:先明确本节要解决的业务问题与核心 API。
  2. 主线实战:跟随课程实现可运行功能(先跑通,再优化)。
  3. 原理深挖:理解为什么这样设计,以及常见误区。
  4. 练习挑战:完成 L1/L2(阶段收官课建议加 L3)巩固迁移能力。
  5. 本节小结:回顾“做了什么 / 学到了什么 / 下节前检查项”。

建议节奏:阅读 20% + 编码 60% + 复盘 20%。

🎯 本节目标:从单页面(SPA)升级为多页面应用,搭建任务管理系统的外壳。

📦 本节产出:带有四个独立页面(首页、项目列表、任务看板、设置)的基础路由骨架。

一、Phase 2 整体规划:任务管理系统

Phase 1 的 Todo App 只有一个页面,管理一个列表。 Phase 2 我们要开发一个 Trello 式的任务管理系统,包含:

  • 多页面体验:React Router v7
  • 复杂状态共享:Zustand
  • 服务端模拟:TanStack Query(Mock API)
  • 专业级 UI:shadcn/ui + Tailwind v4

1.1 项目结构预览


二、初始化 Phase 2 项目

重新创建一个空项目(或者在你现有的代码库旁边新建一个文件夹):

bash
npm create vite@latest phase2-task-manager -- --template react-ts
cd phase2-task-manager
npm install
npm install tailwindcss @tailwindcss/vite

配置 Tailwind v4(修改 vite.config.tssrc/index.css,同 Lesson 01)。

清理无用模板代码后,安装 React Router v7:

bash
npm install react-router

TIP

React Router v7 的变化 v7 时代官方更推荐围绕 react-router 使用 Data Router 能力。本课示例统一从 react-router 导入。 如果你看到旧教程使用 react-router-dom,那通常是 v6 时代或传统组件路由写法。


三、路由基础:搭建 4 个基本页面

先在 src/pages 目录下创建 4 个极其简单的占位组件:

tsx
// src/pages/Home.tsx
export default function Home() {
  return <div className="p-8"><h1 className="text-2xl font-bold">🏠 首页看板</h1></div>
}

// src/pages/Projects.tsx
export default function Projects() {
  return <div className="p-8"><h1 className="text-2xl font-bold">📂 项目列表</h1></div>
}

// src/pages/ProjectBoard.tsx (注意:这是一个动态页面)
import { useParams } from 'react-router'
export default function ProjectBoard() {
  const { id } = useParams() // 获取 URL 中的参数
  return <div className="p-8"><h1 className="text-2xl font-bold">📋 看板 ID: {id}</h1></div>
}

// src/pages/Settings.tsx
export default function Settings() {
  return <div className="p-8"><h1 className="text-2xl font-bold">⚙️ 设置页</h1></div>
}

// src/pages/NotFound.tsx
import { Link } from 'react-router'
export default function NotFound() {
  return (
    <div className="p-8 text-center">
      <h1 className="text-4xl drop-shadow-sm text-red-500 font-bold mb-4">404 - 页面迷路了</h1>
      <Link to="/" className="text-indigo-600 hover:underline">返回首页</Link>
    </div>
  )
}

四、配置顶层路由 (Data Router 模式)

React Router v6.4+ 引入了基于对象的数据路由 (Data Router) 模式,v7 也是强推这种写法(而不是传统的 <Routes> 组件嵌套),因为它支持高级的数据加载 (Loader) 特性。

替换 src/main.tsx

tsx
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { createBrowserRouter, RouterProvider } from 'react-router'
import './index.css'

// 导入页面
import Home from './pages/Home'
import Projects from './pages/Projects'
import ProjectBoard from './pages/ProjectBoard'
import Settings from './pages/Settings'
import NotFound from './pages/NotFound'

// 1. 定义路由配置数组
const router = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
    errorElement: <NotFound /> // 全局 404/错误 边界
  },
  {
    path: '/projects',
    element: <Projects />,
  },
  {
    path: '/projects/:id',  // 动态路由段 (:id)
    element: <ProjectBoard />,
  },
  {
    path: '/settings',
    element: <Settings />,
  }
])

// 2. 将 router 注入到应用根节点
createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>,
)

此时启动项目:

  • 访问 http://localhost:5173/ → 显示首页
  • 访问 http://localhost:5173/projects → 显示项目列表
  • 访问 http://localhost:5173/projects/todo-app → 页面会用 useParams 抓取并显示"看板 ID: todo-app"
  • 访问 http://localhost:5173/aaa → 显示 404

五、用 Navbar 实现页面跳转

多页面应用不能直接用 <a href="/..."> 跳转,因为那会触发完整的浏览器刷新,丢失 React 的所有状态!我们需要用 <Link> 组件。

创建一个简单的临时导航条(下节课我们会升级为完整的 Layout + Sidebar):

tsx
// src/App.tsx (我们把它改造成主应用的壳)
import { Link, Outlet } from 'react-router'

export default function App() {
  return (
    <div className="min-h-screen bg-gray-50 flex flex-col">
      {/* 顶部导航条 */}
      <nav className="bg-indigo-600 text-white p-4 shadow-md flex gap-6">
        <Link to="/" className="font-bold hover:text-indigo-200">🚀 TaskApp</Link>
        <div className="flex gap-4 ml-8">
          <Link to="/" className="hover:text-indigo-200">首页</Link>
          <Link to="/projects" className="hover:text-indigo-200">项目</Link>
          <Link to="/settings" className="hover:text-indigo-200">设置</Link>
        </div>
      </nav>

      {/* 页面内容占位符 */}
      <main className="flex-1">
        <Outlet /> 
      </main>
    </div>
  )
}

等一下!App.tsx 写好了,但怎么让它生效呢?我们需要用到嵌套路由

main.tsx 中的路由配置包裹起来:

tsx
// src/main.tsx (部分修改)
import App from './App'

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,         // 外层包裹组件
    errorElement: <NotFound />,
    children: [               // 内部嵌套页面
      { index: true, element: <Home /> },           // 默认子路由 (/)
      { path: 'projects', element: <Projects /> },
      { path: 'projects/:id', element: <ProjectBoard /> },
      { path: 'settings', element: <Settings /> }
    ]
  }
])

深入理解 <Outlet />

在这个结构下,无论你在哪个子页面切换,顶部的 Navbar 都永远不会被销毁和重新渲染(除非顶级 URL 改变),只有 <Outlet> 区域的内容在被替换,这就是 SPA 路由极其丝滑的原因。


六、🧠 深度专题:SPA 路由原理

传统的 MPA(多页应用):

  1. 点击 <a href="/about">
  2. 浏览器卸载当前页面,向服务器请求 /about 的 HTML
  3. 服务器返回全新 HTML
  4. 浏览器重新解析、加载 CSS/JS,白屏闪烁

React SPA(单页应用)使用 Client-Side Routing (客户端路由)

这里面最核心的浏览器 API 是 History API

  • history.pushState(): 改变 URL 而不刷新页面
  • window.addEventListener('popstate', ...): 监听用户点击浏览器的"后退/前进"按钮

React Router 就是在这两者的基础上包了一层极其庞大和完善的 React 组件状态管理层。


七、练习

  1. Projects.tsx 页面中,手动写死三个项目的假数据,并使用 <Link to="/projects/1"> 等链接指向它们。
  2. 体验 NavLink:导入 import { NavLink } from 'react-router' 替换 App.tsx 中的 LinkNavLinkclassName 可以接收一个函数 ({ isActive }) => ...,试着让当前激活的导航菜单变成高亮的颜色。

📌 本节小结

你做了什么你学到了什么
搭建了任务管理系统的 4 个骨架页面v7 Data Router 的 createBrowserRouter
使用嵌套路由包裹页面childrenindex: true 路由
创建了包含 Outlet 的全局 App父布局组件与 Outlet 插槽协作
实现了无刷新的 SPA 跳转<Link> 替代 <a href>
SPA 客户端路由原理 (History API)

八、真实问题案例:刷新 404 与权限误判

8.1 为什么本地正常、上线刷新就 404?

SPA 的子路由(如 /projects/1)在浏览器刷新时会直接请求服务器该路径。若服务器未配置回退到 index.html,就会返回 404。

  • Vercel / Netlify:通常内置回退能力
  • Nginx:需要 try_files $uri $uri/ /index.html;

8.2 路由守卫常见误区

  • 误区 1:只做前端守卫,不做服务端鉴权(可被绕过)
  • 误区 2:把权限判断写死在组件内,导致多处重复
  • 误区 3:未区分“未登录”和“无权限”,造成错误提示混乱

建议:路由层做体验守卫,服务端做最终授权。

8.3 验收标准(L1/L2)

  1. L1:实现 4 个页面互跳,并正确高亮当前菜单。
  2. L2:在本地模拟刷新子路由场景,给出 404 修复配置(Vercel/Netlify/Nginx 任一)。

项目驱动 · 边写边学 · React 19