Skip to content

React Router 路由(一) ​

本页对应 React Router 路由,共 8 段视频,约 55 分钟。

学习目标 ​

  • 能够说明前端路由“路径与组件一一对应”的基本概念,并使用 createBrowserRouter 配置 path 与 element,通过 RouterProvider 注入路由实例,访问 /login、/article 验证对应内容渲染。
  • 能够将路由配置从入口文件抽离为独立 router 模块,创建页面级路由组件,并在入口中注入导出的路由实例,验证原路由功能保持不变。
  • 能够分别使用 Link 的 to 属性和 useNavigate 实现声明式、编程式导航跳转,并通过浏览器验证跳转结果。
  • 能够使用 useSearchParams 接收查询参数,使用 useParams 接收动态路由参数,并正确配置动态参数占位符,验证参数传递与读取结果。
  • 能够使用 children 配置嵌套路由、使用 Outlet 指定二级路由出口,并通过点击导航验证二级路由内容渲染。
  • 能够配置 index 默认二级路由和 path: "*" 的 404 兜底路由,并能够切换 createBrowserRouter 与 createHashRouter,验证两种路由模式的 URL 表现。

知识点 ​

先理解前端路由与最小环境 ​

为什么需要: 前端路由解决“一个路径对应一个组件”的页面切换问题,访问 /login 渲染登录页,访问 /article 渲染文章页。
怎么想: 先不急着抽象,用最小 demo 跑通“路由表 + 注入路由实例”这条链路。
怎么做: 用 CRA 创建项目,安装核心包并启动:

bash
npx create-react-app react-router-pro
cd react-router-pro
npm i react-router-dom
npm start

易错点: 包名是 react-router-dom,不是 react-router;安装后到 package.json 的 dependencies 里确认版本。

最小路由:path 与 element 对应 ​

为什么需要: 路由系统要先声明“哪个路径渲染哪个内容”,否则浏览器地址变化时页面不知道渲染谁。
怎么想: 用 createBrowserRouter 接收数组,数组项里配置 path 和 element,再用 RouterProvider 把生成的 router 注入应用。
怎么做: 入口文件先写最小版:

jsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

const router = createBrowserRouter([
  { path: '/login', element: <div>我是登录页</div> },
  { path: '/article', element: <div>我是文章页</div> },
])

// 用 RouterProvider 替换原来的 App
<RouterProvider router={router} />

易错点: element 要写 JSX;RouterProvider 的 router 属性要绑定上面创建的实例。调试时手动输入 /login、/article,分别能看到对应内容即生效。

抽象路由模块:页面组件与 router 分离 ​

为什么需要: 把页面内容直接写在入口文件不合理,实际项目要把页面级组件和路由配置拆开,后续加路由只改路由模块。
怎么想: pages 放路由级组件,router 放路由表,入口只负责注入。
怎么做:

jsx
// src/pages/Login/index.jsx
export default function Login() {
  return <div>我是登录页</div>
}

// src/pages/Article/index.jsx
export default function Article() {
  return <div>我是文章页</div>
}
jsx
// src/router/index.jsx
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login'
import Article from '../pages/Article'

const router = createBrowserRouter([
  { path: '/login', element: <Login /> },
  { path: '/article', element: <Article /> },
])

export default router

入口改为导入 router,继续用 <RouterProvider router={router} />。
易错点: 页面组件放 pages,路由配置放 router;入口不要再手写路由表。刷新 /login、/article,效果应与最小版一致。

声明式与编程式导航 ​

为什么需要: 多个路由不是孤立的,登录页需要跳到文章页,就要进行路由导航。
怎么想: 模板里用 Link 做声明式跳转;逻辑代码里用 useNavigate 做编程式跳转。
怎么做:

jsx
import { Link, useNavigate } from 'react-router-dom'

function Login() {
  const navigate = useNavigate()
  return (
    <>
      <Link to="/article">跳转到文章页</Link>
      <button onClick={() => navigate('/article')}>跳转到文章页</button>
    </>
  )
}

调试时点击 Link,地址变为 /article,内容切换;查看元素可见 Link 被解析为 a,href 是 to 的值。
易错点: to 要和路由表 path 对应;useNavigate 必须在组件内调用,不能在组件外调用。

查询字符串传参:useSearchParams ​

为什么需要: 跳转时可能要带参数,例如文章页需要 id 和 name。
怎么想: 查询字符串传参在路径后拼 ?id=1001&name=jack,接收时用 useSearchParams。
怎么做:

jsx
navigate('/article?id=1001&name=jack')

import { useSearchParams } from 'react-router-dom'
const [searchParams] = useSearchParams()
const id = searchParams.get('id')
const name = searchParams.get('name')

调试时地址栏出现 ?id=1001&name=jack,目标页能渲染 1001 和 jack。
易错点: useSearchParams 返回数组,要解构;get 的参数名必须和拼接名一致。

动态路由传参:useParams ​

为什么需要: 查询字符串会带问号,有些场景希望路径更短,如 /article/1001。
怎么想: 路径参数要在路由表用 :id 占位,接收时用 useParams。
怎么做:

jsx
navigate('/article/1001')

// router
{ path: '/article/:id', element: <Article /> }

// Article
import { useParams } from 'react-router-dom'
const { id } = useParams()

调试时 /article/1001 能匹配,页面拿到 1001;多个参数继续写 :name。
易错点: 路由表必须配置 :id 占位符,否则路径匹配不上;useParams 取出的属性名要和占位符一致。

嵌套路由与 Outlet ​

为什么需要: 后台管理常见“一级 Layout 内嵌二级页面”,点击菜单只切换右侧内容。
怎么想: 一级路由用 children 配二级路由,一级组件里用 <Outlet /> 决定二级路由渲染位置。
怎么做:

jsx
{
  path: '/',
  element: <Layout />,
  children: [
    { path: 'board', element: <Board /> },
    { path: 'about', element: <About /> },
  ],
}
jsx
// Layout
<Link to="/board">面板</Link>
<Link to="/about">关于</Link>
<Outlet />

调试时点击“面板”“关于”,内容在 Outlet 位置切换。
易错点: 二级路由放在 children;Outlet 放哪里,二级内容就渲染在哪里。

默认二级路由与 404 ​

为什么需要: 访问 / 时默认显示面板;用户输入错误路径时要有 404 兜底提示。
怎么想: 默认二级路由用 index: true 并去掉 path;404 在路由表末尾用 path: '*'。
怎么做:

jsx
{ index: true, element: <Board /> }

// 404 放数组末尾
{ path: '*', element: <NotFound /> }

默认面板的 Link 改为 to="/"。调试刷新 / 能直接看到面板;手动输入 /a 显示 404。
易错点: index 路由不要再写 path;404 必须放末尾,星号表示通配兜底。

两种路由模式 ​

为什么需要: 不同部署环境对 URL 和后端支持要求不同,需要选择 history 或 hash 模式。
怎么想: createBrowserRouter 是 history 模式,createHashRouter 是 hash 模式,其余配置不变。
怎么做:

jsx
import { createHashRouter } from 'react-router-dom'
const router = createHashRouter([...])

调试时切换到 hash 后,地址会出现 #。
易错点: history 模式通常需要后端配合;hash 模式前端可独立处理,但 URL 带 #。替换 API 后不要改动其他路由配置。

前端路由到底在做什么 ​

为什么需要:传统页面跳转往往要重新请求整页资源;而在单页应用里,我们希望地址变化时只切换页面中的某一块内容,用户体验更连贯。前端路由就是用来建立“地址”和“页面内容”之间对应关系的机制。

怎么想:把路由表理解成一张映射表:一个路径 path 对应一个组件 component。当浏览器访问某个 path 时,路由器就查表,把匹配到的组件渲染到页面中。比如访问 /login 渲染登录页,访问 /article 渲染文章页,这就是前端路由。

怎么做:在 React Router 中,路由表里通常写成 path 和 element 的对应关系。element 既可以放组件,也可以放常规 JSX 元素。访问不同路径时,React Router 会根据配置找到对应内容并渲染。

前端路由中路径与组件对应关系示意图

易错点:不要把“路径变化”理解成“整页刷新”;也不要以为 path 和组件是随便写的,二者必须在路由表里明确对应。路径匹配不到时,当前页面可能什么都不渲染,后续才需要用 404 兜底。

从零配置一个最小路由 ​

为什么需要:只看概念不够,必须把路由系统跑起来,才能验证“访问路径 → 渲染组件”这件事真的生效。

怎么想:最小示例只需要两个路由:/login 和 /article。先创建路由实例,再用 RouterProvider 把实例注入应用入口。转写稿里的推导是:createBrowserRouter 负责接收路由数组并返回 router 实例;RouterProvider 负责把这个实例接入 React 应用。

怎么做:在入口文件中导入 createBrowserRouter 和 RouterProvider,配置路由并绑定:

jsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

const router = createBrowserRouter([
  { path: '/login', element: <div>我是登录</div> },
  { path: '/article', element: <div>我是文章页</div> }
])

root.render(<RouterProvider router={router} />)

调试时启动项目,在浏览器手动输入 /login,页面显示“我是登录”;再输入 /article,显示“我是文章页”,说明路由系统已生效。

易错点:element 里写的是 JSX,不是组件名本身;RouterProvider 的属性是 router,不要写成别的名字;路径要写清楚,如 /login,不要漏掉前面的斜杠。

把路由配置抽离成独立模块 ​

为什么需要:最小示例把路由直接写在入口文件里,真实项目会让入口越来越乱。转写稿在视频末尾也提出:路由组件通常比较复杂,不应该长期用简单 <div> 直接写在入口里。

怎么想:把“页面级组件”放到 pages 目录,把“路由配置”放到 router 模块。入口文件只负责导入路由实例并注入 RouterProvider。

怎么做:先建页面组件:

jsx
// pages/Login/index.jsx
export default function Login() {
  return <div>我是登录页</div>
}

// pages/Article/index.jsx
export default function Article() {
  return <div>我是文章页</div>
}

再建路由模块:

jsx
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login'
import Article from '../pages/Article'

const router = createBrowserRouter([
  { path: '/login', element: <Login /> },
  { path: '/article', element: <Article /> }
])

export default router

入口文件改为:

jsx
import { RouterProvider } from 'react-router-dom'
import router from './router'

root.render(<RouterProvider router={router} />)

调试时刷新页面,再在 /login 和 /article 之间切换,功能应与之前一致,但结构更清晰。

易错点:组件要正确 export default,导入路径要对应;element 中要写 <Login />,不能写成 Login;抽离后入口文件不要再保留旧的路由配置,避免重复或冲突。访问未知路径时当前仍会空白,这属于后续 404 路由要解决的问题。

创建项目与安装依赖 ​

为什么需要:先有一个能运行的路由环境,后续的跳转、传参、嵌套路由才有地方验证。怎么想:沿用 create-react-app 创建项目,再单独安装 React Router 的核心包。怎么做:执行 npx create-react-app react-router-pro,进入项目后安装 react-router-dom,再 npm start 启动。转写稿中安装后看到依赖为 6.9.0 版本。易错点:包名是 react-router-dom,不要漏掉 -dom;项目名不要写成中文或带空格。

bash
npx create-react-app react-router-pro
cd react-router-pro
npm install react-router-dom
npm start

最小路由系统 ​

为什么需要:先理解“一个 path 对应一个 component/element”,用最小 demo 把路由跑起来。怎么想:用 createBrowserRouter 配置路由表,用 RouterProvider 把 router 实例注入应用。怎么做:在入口文件中导入这两个 API,配置 /login 和 /article,element 先写简单 JSX。调试时手动输入 /login 显示“我是登录”,输入 /article 显示“我是文章页”,说明路由生效。易错点:React Router 配置项叫 element,不是 Vue 的 component;element 接收 JSX,不是字符串。

jsx
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

const router = createBrowserRouter([
  { path: '/login', element: <div>我是登录</div> },
  { path: '/article', element: <div>我是文章页</div> }
])

root.render(<RouterProvider router={router} />)

抽象路由模块 ​

为什么需要:把页面内容直接写在入口文件里不科学,实际项目要拆分页面组件和路由配置。怎么想:页面级组件放到 pages,路由配置放到 router,入口只负责注入。怎么做:创建 pages/Login/index.js、pages/Article/index.js,在 router/index.js 中导入组件并配置 path/element,导出 router;入口使用 RouterProvider。刷新后 /login、/article 仍能正常显示。易错点:页面组件要导出并在路由文件中导入;入口不要重复配置路由表。

jsx
// router/index.js
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login'
import Article from '../pages/Article'

export const router = createBrowserRouter([
  { path: '/login', element: <Login /> },
  { path: '/article', element: <Article /> }
])

声明式与编程式导航 ​

为什么需要:多个路由之间要能跳转。怎么想:模板中的菜单用 Link,逻辑代码中的跳转用 useNavigate。怎么做:Link 的 to 写目标路径;useNavigate 得到 navigate 方法,在事件中调用。调试时点击 Link,浏览器会把它解析成 <a>;点击按钮也能跳到文章页。易错点:Link 的 to 属性不能拼错;useNavigate 必须在组件内部调用。

jsx
import { Link, useNavigate } from 'react-router-dom'

const navigate = useNavigate()
<Link to="/article">跳转到文章页</Link>
<button onClick={() => navigate('/article')}>跳转到文章页</button>

两种传参方式 ​

为什么需要:跳转时经常要携带 ID、name 等参数。怎么想:查询字符串用 useSearchParams,路径参数用 useParams。怎么做:search 传参写成 /article?ID=1001&name=jack,接收时 params.get('ID');params 传参写成 /article/1001/jack,路由配置 article/:ID/:name,接收时 params.ID。调试中页面能显示 1001 和 jack。易错点:params 传参必须在路由 path 中写占位符,否则匹配不到;useSearchParams 得到的是对象,取值要调用 get。

jsx
navigate('/article?ID=1001&name=jack')
const [params] = useSearchParams()
const id = params.get('ID')

// 路由:{ path: 'article/:ID/:name', element: <Article /> }
const params = useParams()
const id = params.ID

嵌套路由与默认二级路由 ​

为什么需要:后台布局常是“一级 Layout + 二级内容区”。怎么想:一级路由用 children 配二级路由,一级组件中用 Outlet 标出口。怎么做:配置 / 渲染 Layout,children 配 board、about;Layout 中放 Link 和 Outlet。默认二级路由把 board 的 path 去掉,加 index: true,访问 / 时默认显示面板。易错点:忘记写 Outlet 时二级路由没有渲染位置;默认二级路由不要再保留原 path。

jsx
{
  path: '/',
  element: <Layout />,
  children: [
    { index: true, element: <Board /> },
    { path: 'about', element: <About /> }
  ]
}

404 路由 ​

为什么需要:用户输入错误地址时要有兜底提示。怎么想:在路由表末尾配置通配符 *。怎么做:准备 NotFound 组件,配置 { path: '*', element: <NotFound /> }。输入不存在的 /a 时显示“页面跑到月球了”。易错点:404 路由要放在数组末尾,否则可能抢先匹配。

jsx
{ path: '*', element: <NotFound /> }

两种路由模式 ​

为什么需要:不同部署环境对 URL 和后端配置要求不同。怎么想:history 模式地址干净但需后端支持;hash 模式地址带 #,前端可独立处理。怎么做:把 createBrowserRouter 换成 createHashRouter,其余配置不变,刷新后地址出现 #。易错点:切换模式只换创建 API,不要改路由表结构;实际选型要结合后端是否配合。

课件图示 ​

课件第21页:各个主流框架的路由常用的路由模式有俩种,history模式和hash模式, Re

课件第15页:嵌套路由配置 实现步骤: 1. 使用 children 属性配置路由嵌套关系 2

课件第4页:快速开始 需求:创建一个可以切换登录页和文章页的路由系统 登录 文章 /logi

课件第8页:什么是路由导航 路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能

课件第9页:声明式导航 声明式导航是指通过在模版中通过  组件描述出要跳

课件第10页:编程式导航 编程式导航是指通过  钩子得到导航方法,然

课件第12页:路由导航传参 searchParams 传参 params 传参

课件第14页:什么是嵌套路由 在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级

代码示例 ​

示例一:最小可运行的路由 demo(入口文件直接配置) ​

jsx
// src/index.jsx  (CRA 项目入口,可直接覆盖 src/index.js 内容)
import React from 'react'
import ReactDOM from 'react-dom/client'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

// 第一步:创建 router 实例,并配置 path 与 element 的对应关系
const router = createBrowserRouter([
  {
    path: '/login',
    element: <div>我是登录页</div>
  },
  {
    path: '/article',
    element: <div>我是文章页</div>
  }
])

// 第二步:用 RouterProvider 组件把 router 实例注入到应用中
ReactDOM.createRoot(document.getElementById('root')).render(
  <RouterProvider router={router} />
)

关键行与易错点

  • createBrowserRouter 是一个方法(返回 router 实例),RouterProvider 是一个组件,两者都从 react-router-dom 导入,不要漏掉 -dom。
  • 路由表中用的是 path + element,不是 Vue 里的 component;element 既可以写组件 <Login />,也可以写普通 JSX 元素 <div>。
  • RouterProvider 的属性名固定是 router(不是 routes),值必须是 createBrowserRouter 的返回值。
  • 原来的 <App /> 要移除,否则路由系统不会生效;访问地址需手动输入 /login、/article 验证。
  • 这种“把路由写进入口文件”的写法只适合演示,实际开发要抽成独立模块(见示例二)。

示例二:抽象路由模块 + 嵌套路由 + 默认二级路由 + 404 ​

jsx
// src/pages/Layout/index.jsx —— 一级路由组件(含二级路由出口)
import { Link, Outlet } from 'react-router-dom'

export default function Layout() {
  return (
    <div>
      <div>我是一级路由 Layout</div>
      <Link to="/">面板</Link>
      <Link to="/about">关于</Link>
      {/* 二级路由的渲染位置,写在哪里就渲染在哪里 */}
      <Outlet />
    </div>
  )
}
jsx
// src/pages/Board/index.jsx
export default function Board() {
  return <div>我是面板</div>
}

// src/pages/About/index.jsx
export default function About() {
  return <div>我是关于</div>
}

// src/pages/NotFound/index.jsx
export default function NotFound() {
  return <div>页面跑到月球了</div>
}
jsx
// src/router/index.jsx —— 路由模块,统一管理路由表
import { createBrowserRouter } from 'react-router-dom'
import Layout from '../pages/Layout'
import Board from '../pages/Board'
import About from '../pages/About'
import NotFound from '../pages/NotFound'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    // children 配置嵌套(父子)关系
    children: [
      // index: true 表示默认二级路由,访问 "/" 时默认渲染 Board
      { index: true, element: <Board /> },
      // 子路由 path 不要加前导斜杠
      { path: 'about', element: <About /> }
    ]
  },
  // 404 兜底必须放在数组末尾,* 是通配符
  { path: '*', element: <NotFound /> }
])

export default router
jsx
// src/index.jsx —— 入口只负责注入 router
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router'

ReactDOM.createRoot(document.getElementById('root')).render(
  <RouterProvider router={router} />
)

关键行与易错点

  • 页面级组件放在 src/pages(Vue 里常叫 views),每个组件一个文件夹,方便后续加样式文件;路由表放 src/router,入口只保留 RouterProvider。
  • 嵌套路由靠 children 属性;子路由的 path 不要写成 /about,写相对路径 about,否则匹配不到。
  • 二级路由的出口是 <Outlet />,忘记写它则子路由内容无处渲染,页面看起来“跳转没反应”。
  • 默认二级路由:把 path 去掉,改为 index: true;同时 Layout 里指向面板的 Link to="/board" 必须改成 to="/",否则会走到 404。
  • 404 的 path: '*' 必须放在路由数组最后,因为匹配是从上到下的兜底逻辑。

示例三:路由导航跳转与两种传参方式 ​

jsx
// src/pages/Login/index.jsx —— 声明式导航 + 编程式导航
import { Link, useNavigate } from 'react-router-dom'

export default function Login() {
  // 钩子只能在组件内部调用,返回一个跳转方法
  const navigate = useNavigate()

  return (
    <div>
      <div>我是登录页</div>

      {/* 声明式导航:to 是固定属性,写目标路由 path */}
      <Link to="/article">跳转到文章页</Link>

      {/* searchParams 传参:路径后用 ? 拼接,多个参数用 & 连接 */}
      <Link to="/article?id=1001&name=jack">searchParams 传参</Link>

      {/* params 传参:直接拼在路径上,没有问号 */}
      <Link to="/article/1001/jack">params 传参</Link>

      {/* 编程式(命令式)导航,适合登录成功后的逻辑跳转 */}
      <button onClick={() => navigate('/article/1001/jack')}>命令式跳转</button>
    </div>
  )
}
jsx
// src/pages/Article/index.jsx —— 接收参数
import { useSearchParams, useParams } from 'react-router-dom'

export default function Article() {
  // searchParams 取值:返回数组,解构出 params 对象,再用 get 方法取
  const [searchParams] = useSearchParams()
  const id = searchParams.get('id')
  const name = searchParams.get('name')

  // params 取值:返回对象,直接点语法
  const { id: pathId, name: pathName } = useParams()

  return (
    <div>
      <div>我是文章页</div>
      <div>searchParams 的 id:{id}</div>
      <div>searchParams 的 name:{name}</div>
      <div>params 的 id:{pathId}</div>
      <div>params 的 name:{pathName}</div>
    </div>
  )
}
jsx
// src/router/index.jsx —— 注意 params 传参必须配置占位符
import { createBrowserRouter } from 'react-router-dom'
import Login from '../pages/Login'
import Article from '../pages/Article'
import NotFound from '../pages/NotFound'

const router = createBrowserRouter([
  { path: '/login', element: <Login /> },
  { path: '/article', element: <Article /> },
  // 冒号后是占位符名字,和 useParams 解构的名字一一对应
  { path: '/article/:id/:name', element: <Article /> },
  { path: '*', element: <NotFound /> }
])

export default router

关键行与易错点

  • 声明式导航用 <Link to="路径">,渲染后会被解析成 <a> 标签,to 的值必须与路由表里的 path 对应。
  • 编程式导航必须用 useNavigate(),它只能在组件函数体内调用,写在组件外面会报 Hook 调用错误。
  • useSearchParams 的返回值是数组(类似 useState),要写成 const [params] = useSearchParams(),取参数用 params.get('id');少写中括号直接调用 .get 会报错。
  • useParams 的返回值是对象,直接点语法取值,但前提是路由表里配了 :id 这样的占位符——漏配占位符是 params 传参最常见的失败原因。
  • 两种传参方式的路由配置不同:/article?id=1001 匹配 path: '/article';/article/1001/jack 必须匹配 path: '/article/:id/:name',路径段数不一致就落到 404。

常见错误 ​

1. 页面白屏,控制台报 “useNavigate() may be used only in the context of a <Router>”

  • 现象:组件一渲染就报错崩溃,或点击按钮无反应。
  • 原因:入口文件里仍然渲染的是 <App />,<RouterProvider> 没挂上;或者把 useNavigate / useSearchParams 这类钩子写在了被 RouterProvider 包裹的层级之外(例如入口里直接调用)。
  • 修法:入口只保留 <RouterProvider router={router} />,把需要导航逻辑的代码挪进路由表 element 指向的组件内部。

2. element 写成 {Login} 或 Login(),组件不渲染 / 报错

  • 现象:路径匹配上了,但页面空白或抛异常。
  • 原因:element 接收的是 JSX 节点,不是组件引用,也不是函数调用结果。
  • 修法:统一写 <Login />;同时检查是不是把 component 这个 Vue 习惯的字段名写了进来。

3. 点击链接跳不过去,或跳过去是空白页

  • 现象:URL 变了,但内容没渲染,或直接落到 404。
  • 原因:path 少写前导 /;Link 的 to 与 path 大小写、层级不一致;嵌套子路由的 path 写成了 /board 这种绝对路径导致匹配不上。
  • 修法:一级 path 统一以 / 开头,子路由 path 不写前导斜杠,to 与 path 逐字符对齐后再测。

4. 路径传参时 params.id 恒为 undefined

  • 现象:useParams() 拿到空对象。
  • 原因:路由表里忘了加 :id 占位符;或占位符名字与解构出来的字段名不一致(大小写也算)。
  • 修法:path: '/article/:id' 与 const { id } = useParams() 严格同名;这类传参“占位符”是必做步骤,不能只改跳转地址。

5. 查询串传参取不到值

  • 现象:searchParams.get('id') 返回 null。
  • 原因:跳转地址写成了 /article/1001(路径风格),接收端却用 useSearchParams;或拼参数时漏了 ?、多个参数之间漏了 &。
  • 修法:先确定用哪条传参路线,传和收必须成对;?a=1&b=2 配 useSearchParams,/article/1001 配 useParams。

6. 嵌套路由点击菜单,二级内容不显示

  • 现象:一级组件渲染了,二级出口处始终空白。
  • 原因:一级组件里漏了 <Outlet />;或把 children 写在了错误的对象层级上。
  • 修法:在一级组件需要出内容的位置放 <Outlet />,并确认 children 数组挂在对应的一级路由对象内。

7. 配了默认二级路由后,点击菜单报错或路径重复

  • 现象:点“面板”跳到 /board/board 或直接 404。
  • 原因:改成默认路由后仍保留了 path;或 Link 的 to 没跟着改。
  • 修法:默认路由删掉 path、只留 index: true,对应链接地址同步改成一级路径 /。

8. 输入错误地址仍白屏,404 组件不生效

  • 现象:随便敲个 /xxx 什么都没有。
  • 原因:path: '*' 没放在路由数组最后一项,被前面的规则抢先;或写进了 children 里。
  • 修法:* 永远放路由表末尾,作为兜底项。

练习 ​

练习 1|环境与最小路由 用 CRA 建项目并安装 react-router-dom,实现 /login、/article 两条路由,手动改地址栏验证渲染;然后把两个页面拆成独立组件文件,新建 router/index.js 统一配置,入口只留 RouterProvider。 验收:新增第三个页面时,只需新建组件 + 改 router/index.js 一处。

练习 2|两种导航方式 在登录页放一个 <Link> 和一个按钮,两者都跳 /article;按钮用 useNavigate 实现,并在跳转前 console.log 一行模拟“登录成功”。 验收:能说清两段代码分别放在模板里还是逻辑里,以及各自适合什么场景。

练习 3|查询串传参 从登录页跳到 /article?tab=hot&id=1001,在文章页用 useSearchParams 取出两个值并渲染;再额外做一个“切到 tab=new”的按钮,用 setSearchParams 改参数。 验收:刷新页面后参数不丢,get 取到的值与地址栏一致。

练习 4|路径传参 把练习 3 改成 /article/:id/:name 形式,同步改路由表占位符、跳转地址、接收端。 验收:故意把占位符名字改错一次,观察 undefined 现象并改回。

练习 5|后台布局 + 兜底 搭一个 / 的 Layout 一级路由,内含 board、about 两个二级路由,用 <Outlet /> 做出口、用 <Link> 做菜单;把 board 设为默认二级路由;最后加 * 兜底 404 组件。 验收:访问 / 直接看到面板内容;点菜单 URL 与内容同步变化;输入 /abc 落到 404;把 createBrowserRouter 换成 createHashRouter 后,其余代码一行不改仍能正常工作。

原视频对照 ​

#视频时长
1ReactRouter-快速开始09:34
2ReactRouter-抽象路由模块07:30
3ReactRouter-路由导航跳转07:48
4ReactRouter-导航跳转传参09:41
5ReactRouter-嵌套路由配置09:13
6ReactRouter-默认二级路由配置03:24
7ReactRouter-404路由配置04:37
8ReactRouter-俩种路由模式03:16

小结 ​

  1. React Router v6 的接入永远是“创建实例 → 注入 Provider”两步,实例是唯一入口,不要在入口再渲染业务根组件。
  2. 路由表必须独立成模块,页面组件放 pages/views,新增页面只改这一处,这是可维护性的分水岭。
  3. element 收 JSX 节点,不是组件引用——这是从 Vue 迁移过来最容易踩的第一个坑。
  4. 传参有两条互斥路线,传与收必须成对;其中路径传参多一步“路由表加占位符”,漏了必出 undefined。
  5. 嵌套路由是 children + <Outlet /> + 子路由相对路径三件套,缺任何一件二级内容都不显示。
  6. 默认二级路由用 index: true 且不能带 path,改完记得同步修改指向它的链接地址。
  7. path: '*' 的优先级由数组顺序决定,兜底路由永远放最后。
  8. 声明式用于模板与菜单,编程式用于逻辑之后(登录成功、请求回调);history 与 hash 只差一个创建 API,选型取决于后端是否配合。