React Router 原来是这么分层的:一次走通一级 / 二级路由

78 阅读6分钟

前言|用一条路线理解 React Router

想象你走进一座大型图书馆,每一扇门都贴着标签,推开不同的门,就能进入不同主题的书架区域。

网页里的「点击、跳转、页面切换」本质上也是同一件事。
React 路由(react-router)正是这套门牌 + 指路系统:URL 像门牌号,组件像房间,它负责在不刷新页面的前提下,把用户带到正确的位置。

正因为有了它,传统网站那种“翻页书”式的跳转,逐渐被更丝滑的体验取代——
网址在变,内容在切,但页面本身没有重载。

在下面的示例中,我们会从应用的入口开始,一步步走到登录页、主页,再进入具体的功能页面。
随着路径的变化,对应的组件会被准确地渲染出来,你可以边看边敲,直接跑起来。

在开始动手之前,请先在项目中安装官方包 react-router-dom(本文示例基于 v6+)。完成安装后,就可以在组件中使用路由相关能力来控制页面的跳转与渲染。

# 安装 React Router(v6+)
npm install react-router-dom

# 检查是否安装成功
npm list react-router-dom

如果你想进一步了解路由的设计理念和完整用法,可以参考官方文档👇:

reactrouter.com

一、路由的作用:URL 与组件的映射关系

如果用一句话概括路由的作用,那就是:

把 URL 映射到组件,并控制页面切换的过程。

有了路由,我们才能用清晰、有意义的 URL 来表示不同页面,也才能在页面切换时避免刷新,让状态得以保留。同时,像登录成功后的自动跳转、嵌套路由、404 页面这些常见需求,也都依赖路由来完成。

当你理解了这一点之后,路由本身就不再神秘了。
真正需要思考的其实是:页面结构该如何组织。

二、路由分层讲解

2.1 一级路由:应用的整体入口

一级路由决定的是:你现在站在这栋建筑的哪一层、哪个区域。

例如 /login 表示登录页,/home 表示后台主页。
这些「大方向」的路径,通常统一写在 App.jsx 中,由 BrowserRouter + Routes 来集中管理。

// src/App.jsx
import React from 'react'                                  
// 下面引入的路由相关组件后面都会有解释
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import Login from './pages/Login'
import Home from './pages/Home'
import Class from './pages/Class'
import LeetCode from './pages/LeetCode'
import NotFound from './pages/NotFound'

export default function App() {
  return (
    // BrowserRouter:路由总容器,监听地址栏变化
    // 如果项目部署在子路径下,可配置 basename="/app"
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Navigate to="/login" replace />} />
        {/* 一级路由:登录页 */}
        <Route path="/login" element={<Login />} />
        {/* 一级路由:主页(作为二级路由的父容器,后面专门讲)*/}
        <Route path="/home" element={<Home />}>
          {/* index 路由:访问 /home 时的默认页面 */}
          <Route index element={<Class />} />
          {/* 二级路由👇 */}
          <Route path="class" element={<Class />} />
          <Route path="leetcode" element={<LeetCode />} />
        </Route>
        {/* 所有未匹配到的路径,统一显示 404 页面 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  )
}

在这段代码中,涉及了几个一级路由里最核心的角色:

BrowserRouter 是整个应用的最外层容器,它负责接管浏览器地址栏的变化,并把 URL 的变化同步给 React;没有它,后面的路由规则是无法生效的。
Routes 可以理解为一个「路由出口」,它会根据当前的地址,从内部的规则中挑选出最匹配的一条进行渲染。
而每一个 Route,本质上都是一条「路径 → 组件」的映射规则:当 URL 命中某个路径时,对应的组件就会被渲染出来。
至于 Navigate,则更像是一个路由层面的“转向标志”。在这里,我们用它把根路径 / 自动引导到 /login,让用户一进入应用就落在登录页,而不是停在一个空白页面。

App.jsx就像整栋楼的楼层分布图。 用户一进来先决定去哪一层,再在这一层里继续活动。

2.2 二级路由:固定布局下的内容切换

进入 /home 之后,页面结构通常是相对固定的:
侧边栏、头部这些公共区域不会变,真正变化的是中间的内容区。

这正是二级路由要解决的问题:
父组件 Home 负责公共布局,而子路由(如 ClassLeetCode)只负责内容本身。

// src/pages/Home.jsx
import { Outlet, Link } from 'react-router-dom'

export default function Home() {
  return (
    <div style={{ display: 'flex', height: '100vh' }}>
      <aside style={{ width: 200, background: '#f5f5f5', padding: 16 }}>
        <h3>后台管理</h3>
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {/* 相对路径写法,基于当前父路由 /home */}
          <li><Link to="class">课程</Link></li>
          <li><Link to="leetcode">算法</Link></li>
        </ul>
      </aside>

      <main style={{ flex: 1, padding: 20 }}>
        <header style={{ marginBottom: 12 }}>
          <strong>Home 公共布局</strong>
        </header>

        {/* 二级路由内容渲染位置 */}
        <Outlet />
      </main>
    </div>
  )
}

在父路由组件中使用to="class"这样的相对路径,而不是to="/home/class",可以降低路径耦合度。如果将来 /home 改名,只需要改路由配置,不必逐个修改 Link。

下面就来说明 Home.jsx 同时演示的两个重要点:Outlet(子路由渲染点)Link(无刷新导航) 吧。

<Outlet />:子路由的渲染出口

它只是告诉 React —— 子路由渲染到这里。
父组件不需要关心当前显示的是哪个子页面,只负责把位置预留出来,具体显示什么内容,由当前命中的子路由决定。

Link:不刷新页面的导航方式

点击 Link 时,浏览器地址栏里的 URL 会发生变化,但页面不会刷新。
React 会根据新的路径,直接切换渲染对应的组件,就像用遥控器换台一样,内容在变,但屏幕本身没有重启。

Class / LeetCode(二级路由页面示例)
// src/pages/Class.jsx
import React from 'react'

export default function Class() {
  return (
    <div>
      <h2>课程页面</h2>
      <p>这里可以放课程列表或详情。</p>
    </div>
  )
}
// src/pages/LeetCode.jsx
import React from 'react'

export default function LeetCode() {
  return (
    <div>
      <h2>算法练习</h2>
      <p>这里可以放 LeetCode 刷题内容。</p>
    </div>
  )
}

当访问 /home/class/home/leetcode 时,对应组件就会被渲染到 Home 中的 <Outlet /> 位置。

2.3 useNavigate:由代码控制的页面跳转

并不是所有页面跳转,都是用户主动点击链接完成的。
当跳转需要由程序逻辑触发时,就该 useNavigate 登场了。

// src/pages/Login.jsx
import React from 'react'
import { useNavigate } from 'react-router-dom'

export default function Login() {
  const navigate = useNavigate()

  function handleLogin() {
    // 登录逻辑省略
    //这里使用 `replace: true`,可以避免用户点击浏览器“后退”时回到登录页。
    navigate('/home/class', { replace: true })
  }

  return (
    <div style={{ padding: 20 }}>
      <h2>登录页</h2>
      <input placeholder="账号" />
      <input placeholder="密码" type="password" />
      <button onClick={handleLogin}>登录</button>
    </div>
  )
}

useNavigate 的核心作用可以概括为一句话:

让「代码」而不是「用户点击」来决定页面跳转。

Link只负责“点哪里跳哪里”,无法表达流程上的判断与控制;
当需要代码根据如“登录成功了没❓”这样的某个结果来决定是否跳转时,就该用 useNavigate了。


结语|路由不是难点,页面结构才是

当你把这些角色放在一起时,路由的整体结构就会变得非常清晰:
一级路由负责「站在哪一层」,二级路由负责「这一层里看什么内容」。
Outlet 提供内容的展示窗口,Link 负责无刷新切换,而 useNavigate 则让代码也能参与页面流程的控制。

React Router 本身并不复杂,真正的难点在于:你如何组织页面结构。

如果你是刚开始学习 React,这套分层思路会在未来很长一段时间里反复帮到你。
下一步不妨亲手敲一遍这套代码,让你的第一个 SPA真正“活”起来。

如果这篇文章对你有帮助,欢迎点赞 / 收藏 / 关注专栏 ❤️
后续会继续拆解 React 中那些「看起来难,其实有章法」的知识点。