React 从入门到生产(六):路由与导航

创作者: Yardon | GitHub: github.com/YardonYan | 版本: v1.0


单页应用的 URL 问题

传统的多页网站,每个页面有自己的 URL。你在浏览器地址栏输入 /about,服务器就返回 about.html,每一页都是完整独立的一次往返。

React 做的单页应用(SPA)不一样------浏览器加载一个 HTML 文件之后,所有的页面切换都在前端完成,服务器只负责提供 JSON 数据。

这就带来一个问题:URL 怎么变?

用户期望的行为是------点击导航、刷新页面、前进后退都能正常工作。这意味着 React 必须"接管"浏览器的 URL,在不刷新页面的前提下改变地址栏的内容。

React Router 就是做这件事的。它的核心思想很简单:

把 URL 当成一个状态,当它变化时,渲染对应的组件。


React Router v6 快速上手

安装与基础配置

bash 复制代码
npm install react-router-dom
jsx 复制代码
// main.jsx
import { BrowserRouter } from 'react-router-dom';
import App from './App';

createRoot(document.getElementById('root')).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
jsx 复制代码
// App.jsx
import { Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/blog" element={<Blog />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}
jsx 复制代码
import { Link, NavLink } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      {/* 基础链接 */}
      <Link to="/">首页</Link>

      {/* NavLink 会自动给当前页面高亮 */}
      <NavLink
        to="/blog"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        博客
      </NavLink>
    </nav>
  );
}

NavLinkLink 的增强版------它知道自己是不是当前页,可以自动应用激活样式。

编程式导航

有时候需要在代码里控制跳转(比如表单提交成功后跳转到新页面):

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

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

  async function handleSubmit(e) {
    e.preventDefault();
    const result = await login(username, password);
    if (result.success) {
      navigate('/dashboard');  // 成功后跳转
    }
  }
}

路由嵌套与 Outlet

一个常见的布局模式:顶部导航栏、侧边栏是固定的,中间的内容区域跟随路由变化。

复制代码
┌─────────────────────────────┐
│      Navbar (固定)           │
├────────┬────────────────────┤
│Sidebar │  内容区域 (变化)     │
│ (固定) │                    │
│        │                    │
└────────┴────────────────────┘
jsx 复制代码
function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        {/* 这些路由的内容会渲染到 Layout 的 Outlet 位置 */}
        <Route index element={<Home />} />
        <Route path="dashboard" element={<Dashboard />} />
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>
  );
}

function Layout() {
  return (
    <div>
      <Navbar />
      <div className="main">
        <Sidebar />
        <div className="content">
          <Outlet />  {/* 子路由的内容渲染在这里 */}
        </div>
      </div>
    </div>
  );
}

<Outlet /> 就是路由的「插槽」------子路由匹配到哪个组件,它就渲染哪个组件。而 Layout 组件本身保持不变(导航栏、侧边栏不用重新渲染)。这个特性在搭建复杂布局时非常关键。


动态路由与 URL 参数

博客文章页面,每篇文章有一个唯一 ID。URL 大概是 /blog/123,其中 123 是动态的。

jsx 复制代码
<Route path="/blog/:id" element={<BlogPost />} />
jsx 复制代码
import { useParams } from 'react-router-dom';

function BlogPost() {
  const { id } = useParams();  // id = "123"

  const [post, setPost] = useState(null);

  useEffect(() => {
    fetch(`/api/posts/${id}`)
      .then(res => res.json())
      .then(setPost);
  }, [id]);

  if (!post) return <Loading />;
  return <PostDetail post={post} />;
}

导航守卫与重定向

保护需要登录的页面

jsx 复制代码
function ProtectedRoute({ children }) {
  const isLoggedIn = useAuthStore(s => s.isLoggedIn);

  if (!isLoggedIn) {
    // 未登录 → 跳到登录页,并记住想去的页面
    return <Navigate to="/login" replace />;
  }

  return children;
}

// 使用
<Route path="/dashboard" element={
  <ProtectedRoute>
    <Dashboard />
  </ProtectedRoute>
} />

旧路径重定向到新路径

jsx 复制代码
// 旧版路径自动跳转
<Route path="/old-about" element={<Navigate to="/about" replace />} />

查询参数与 useSearchParams

博客列表页的翻页和筛选一般用查询参数实现:/blog?page=2&tag=react

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

function BlogList() {
  const [searchParams, setSearchParams] = useSearchParams();

  const page = Number(searchParams.get('page')) || 1;
  const tag = searchParams.get('tag') || '';

  function goToPage(n) {
    setSearchParams({ page: n, tag });  // 保留其他参数
  }

  return (
    <div>
      {/* 翻页按钮 */}
      <button onClick={() => goToPage(page - 1)} disabled={page === 1}>上一页</button>
      <span>第 {page} 页</span>
      <button onClick={() => goToPage(page + 1)}>下一页</button>
    </div>
  );
}

懒加载与代码分割

当应用变大后,所有页面打包进一个 JS 文件会导致首屏加载很慢。React Router 配合 lazy 实现按需加载:

jsx 复制代码
import { lazy, Suspense } from 'react';

// 每个页面单独打包,访问时才加载
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Suspense 在组件加载过程中显示一个 fallback(加载动画),让用户知道"页面正在加载",而不是看一片空白。


本章小结

概念 一句话总结
BrowserRouter 包装整个应用,启用前端路由
Routes/Route 声明式定义 URL 到组件的映射
Link/NavLink 无刷新的页面跳转,NavLink 自带激活判断
Outlet 嵌套路由的插槽,布局组件渲染子路由内容
useParams 读取动态路由参数 /blog/:id
Navigate 声明式重定向
useSearchParams 读写查询参数 /blog?page=2
lazy + Suspense 按需加载页面,减小首屏包体积

路由是现代前端框架的基石。掌握这些概念后,你就拥有了搭建完整多页 SPA 的能力。下一章,我们聚焦 性能优化------如何让 React 应用更快、更流畅。


📌 创作者: Yardon | 🏠 个人网站: GlimmerAI.top

📖 本章是「React 从入门到生产 」系列的第 6 章。上一章:状态管理选型 | 下一章:性能优化

🌟 如果你觉得有帮助,欢迎访问 GlimmerAI.top 查看我的更多作品。欢迎大家来观看!

相关推荐
大熊背4 小时前
双目拼接竖缝消除(ISP 分区锐化实操方案) 优化方案
人工智能·算法·双目拼接
Sylus_sui4 小时前
实现:每行固定 5 个、自动换行、最后一行左对齐、数量不固定
前端·javascript·css
byzh_rc4 小时前
[DL_Net从入门到入土] 生成对抗网络 GAN
人工智能·生成对抗网络·php
猫头虎4 小时前
【Trea】Trea国内版|国际版|海外版下载|Mac版|Windows版|Linux下载配置教程
linux·人工智能·windows·macos·aigc·ai编程·agi
烟雨江南7854 小时前
从转写到智能体决策:基于“灵声智库”与本地大模型(LLM)的政务热线智能分析与 RAG 知识库融合架构
人工智能·科技·架构·语音识别·政务·ai质检
大可ai中文版镜像4 小时前
OpenAI Codex Desktop App 保姆级安装教程(Windows / Mac)
人工智能·macos·codex
YJlio4 小时前
ChatGPT 2023年5月更新解读:iOS App上线,从网页产品扩展到移动端
人工智能·openai·ai工具·ios app·移动端语音输入·whisper产品分析
文滨4 小时前
10分钟搞定!Mac 配置 GitHub SSH 完全指南(小白也能看懂)
前端·macos·ssh·github
不懒不懒4 小时前
Python+AI 大模型实现课堂教学质量智能分析|加权评分 + 自动诊断 + 改进建议
人工智能·python·深度学习·ai大模型·智慧教育·nlp算法