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 查看我的更多作品。欢迎大家来观看!

相关推荐
A.说学逗唱的Coke39 分钟前
【大模型专题】向量数据库深度解析:从原理到实战,构建企业级 AI 知识检索底座
数据库·人工智能
果丁智能1 小时前
智能锁赋能网约房民宿数字化管控:身份核验+远程授权,筑牢安全防线、降本增效
网络·数据库·人工智能·安全·智能家居
V搜xhliang02461 小时前
AI智能体的数据安全与合规实践
人工智能·学习·数据分析·自动化·ai编程
PPIO派欧云1 小时前
PPIO登上贵州新闻联播,深化AI算力生态建设
人工智能
hai3152475431 小时前
一种通过空间几何转换进行软件编程计算的方式与现有计算的对比
人工智能·深度学习·数学建模·硬件架构·几何学·图论·拓扑学
猿饵块1 小时前
LibreOffice---文档制作
人工智能
硅谷秋水1 小时前
HARBOR:一个面向具身智体机器人强化学习的驾驭框架
人工智能·深度学习·机器学习·机器人
Mr..Jackey1 小时前
瑞佑 RUI Builder 图形化 UI 设计工具
arm开发·人工智能·单片机·ui·人机交互·ra8889·lcd控制芯片
霍格沃兹测试开发学社测试人社区1 小时前
Skills实战:从0到1封装一个“登录鉴权”Skill,拿来即用
人工智能