技术分享
🗒️React18 核心与实战(三)路由(react-router)
00 分钟
2024-11-10
2024-11-10
type
status
date
slug
summary
tags
category
icon
password

前端路由

概念来源于后端 : 一个路径表示匹配一个服务器资源 /a.html -> a对应的文件资源 /b.html -> b对应的文件资源
共同的思想: 一对一的关系
前端的路由: 一个路由path对应一个组件component,当我们浏览器中访问一个path的时候,path对应的组件会在页面中进行渲染.
前端路由是指在不重新加载整个页面的情况下,根据 URL 的变化来控制用户界面展示不同的内容。这种技术使得单页应用(Single Page Application, SPA)在用户体验上更接近多页应用,但在底层上是通过 JavaScript 来处理页面切换,而不是请求服务器来加载新的页面。

主要概念和工作原理

  1. URL 映射到组件:在前端路由中,每一个 URL 都对应着一个页面或一个组件。当 URL 改变时,前端路由器(如 React Router、Vue Router)会识别出这个变化,并渲染相应的组件,而不是刷新整个页面。
  1. 单页应用(SPA):前端路由通常用于 SPA 应用中。整个应用是一个页面,通过路由切换不同的视图来模拟多页效果。初次加载时,浏览器加载了基本的 HTML、CSS 和 JavaScript,以后再切换页面时,只更新视图,而不重新加载整个页面资源。
  1. History API 和 Hash 模式
      • History 模式:基于 HTML5 的 History API,通过 pushStatereplaceState 改变浏览器的地址栏而不刷新页面。适合现代浏览器。
      • Hash 模式:基于 URL 中的哈希(#)标记,通过监听 URL 中的 # 后面的部分实现路由切换。这种方式更兼容旧浏览器。

前端路由的优势

  • 提升用户体验:页面切换速度快,不用频繁刷新和加载资源。
  • SPA 体验:提供了更平滑的导航和更自然的应用体验,适合创建类似于桌面应用的 Web 应用。
  • 降低服务器负担:很多渲染和处理都在客户端完成,减少服务器的响应需求。

开发环境

使用路由我们还是采用CRA创建项目的方式进行基础环境配置。
  1. 创建项目并安装所有依赖
  1. 安装最新的ReactRouter包
  1. 启动项目

基本使用

notion image
 

目录结构

代码实现

1. Login/index.jsArticle/index.js

Login/index.js
Article/index.js

2. 配置路由文件 router/index.js

router/index.js

3. 在 index.js 中渲染 RouterProvider

index.js

案例分析

  • 文件分离:通过将页面组件分离到独立文件夹的 index.js 中,更加模块化,方便管理和查找。
  • RouterProvider 的直接使用:顶层直接提供路由上下文,使路由配置一目了然。

路由导航跳转

路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信。

1. 声明式导航

声明式导航:通过在模板中通过‘<Link /> 组件描述出要跳转到哪里去’,比如后台管理系统的左侧菜单通常使用这种方式进行。
使用 <Link> 组件
<Link> 组件用于在 JSX 中定义一个链接。点击时,不会刷新整个页面,而是进行路由切换。
示例:在 Login 页面添加跳转按钮
在这个例子中,当用户点击 Go to Article 链接时,React Router 会将用户导航到 /article 路径,并渲染 Article 页面。
语法说明:通过给组件的to属性指定要跳转到路由path,组件会渲染为浏览器支持的a链接,如果需要传参直接 通过字符串拼接的方式拼接参数即可。

2. 编程式导航

编程式导航:通过'useNavigate’钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方法,更加灵活。
使用 useNavigate Hook
useNavigate 是 React Router 提供的 Hook,用于在代码中进行编程式导航跳转。这非常适合需要在特定事件(如按钮点击)中进行导航的情况。
示例:在 Login 页面添加一个按钮,点击后跳转到 Article 页面
在这个示例中,useNavigate 返回的 navigate 函数可以调用并传入路径 /article,从而实现页面跳转。
语法说明:通过调用navigate方法传入地址path实现跳转。
补充说明(条件跳转):
有时我们需要在满足特定条件后跳转页面,比如用户登录成功后跳转到首页。这种导航方式可以用 useNavigate 和条件语句来实现。
示例:用户输入用户名后点击按钮跳转
在这个例子中,用户点击 Login 按钮后,如果用户名输入不为空,就会跳转到 Article 页面。

3. 总结

  • 声明式导航:通过 <Link> 组件进行声明式的导航跳转。适合在页面结构中直接定义跳转链接,点击时会触发导航。
  • 编程式导航:通过 useNavigate Hook,在事件处理或逻辑中进行跳转。适合需要条件判断、用户操作或特定事件触发的导航。
 

路由导航传参

notion image

1. URL 参数(路径参数)

适用场景:URL 参数是直接嵌入到路由路径中的参数,通常用于标识资源的唯一标识符,比如用户 ID、文章 ID 等。
定义路径参数的路由
在目标组件中获取 URL 参数
Article 组件中,可以使用 useParams Hook 来获取路径中的 id 参数:
使用 <Link>navigate 传递路径参数

2. 查询参数(Query 参数)

适用场景:查询参数适合用于传递非关键性的、可选的附加信息,如排序方式、过滤条件等。查询参数会以 ?key=value 的形式添加到 URL 中。
在目标组件中获取查询参数
Article 组件中,可以使用 useLocationURLSearchParams 来获取查询参数:
使用 <Link>navigate 传递查询参数

3. state 参数

适用场景state 参数用于传递复杂的对象或不希望直接暴露在 URL 中的信息。适合在页面导航过程中传递非持久性的临时数据。
在目标组件中获取 state 参数
Article 组件中,可以通过 useLocationstate 属性来获取 state 参数:
使用 <Link>navigate 传递 state 参数

总结

传参方式
适用场景
传递方式(示例)
获取方式
URL 参数
关键参数,标识资源的唯一性
<Link to="/article/123" />
useParams()
查询参数
可选附加信息,如排序、过滤条件
<Link to="/article?id=123&sort=asc"/>
useLocation() + URLSearchParams
state 参数
临时性数据,不希望暴露在 URL 中
<Link to="/article" state={{id:123}} />
useLocation().state

嵌套路由

嵌套路由(Nested Routes)是 React Router 中的一种路由配置方式,它允许在一个路由组件内嵌套其他路由,形成一个层次化的路由结构。嵌套路由通常用于实现布局和子页面的嵌套,或者在父级页面中嵌套子页面。
在一级路由中又嵌套了其他路由,这种关系就叫嵌套路由,嵌套至一级路由内的路由又称作二级路由。
notion image

实现步骤:

  1. 使用 children 属性配置路由嵌套关系
    1. children 是用于定义 嵌套的子路由 的配置项。它是父路由配置中的一个属性,里面可以包含多个子路由配置。通过在父路由中配置 children,我们可以实现父子路由的嵌套。
    2. children 是一个数组或对象,数组中包含多个子路由对象。
    3. 每个子路由对象配置了该子路由的路径(path)和要渲染的组件(element
  1. 使用 '<Outlet />' 组件配置二级路由渲染位置
    1. <Outlet /> 是一个占位符组件,用来渲染父路由匹配后所对应的子路由的内容。它的作用是告诉 React Router,在父组件中哪个位置渲染子路由。

嵌套路由的具体使用

1. 配置父路由和子路由

父路由中配置 children 来嵌套子路由,子路由则会在父组件的 <Outlet /> 中渲染。

2. 父路由组件使用 <Outlet />

在父路由的组件中,使用 <Outlet /> 来占位,表示在这里渲染匹配的子路由内容。

3. 子路由组件

子路由组件负责渲染与父路由匹配的内容,并被嵌套在父组件的 <Outlet /> 中。

4. 渲染路由

通过 RouterProvider 来渲染配置好的路由。
 

嵌套路由的优势

  • 层次结构清晰:通过嵌套的方式,可以更清晰地表达页面的层次关系。比如,Dashboard 页面可以作为父级路由,里面嵌套 ProfileSettings 等子页面。
  • 布局复用:父组件可以处理页面布局,子组件只负责具体的内容和功能,避免重复编写布局代码。
  • 动态渲染:可以根据路由的不同层级和路径动态地渲染不同的子页面。

嵌套路由的实际应用场景

  • 管理后台:常见于管理系统,父路由作为大框架,子路由作为具体的功能页面,如:Dashboard → Profile、Dashboard → Settings、Dashboard → Orders 等。
  • 用户中心:用户中心可以有多个子页面,如个人资料、账户设置等。
  • 内容管理系统(CMS):在一个复杂的内容管理系统中,嵌套路由可以帮助组织不同的页面视图,并且通过 <Outlet /> 让页面呈现不同的内容。

特殊路由

默认路由

当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性值为true。
在 React Router 中,"默认二级路由"通常指的是在嵌套路由中,某个子路由默认被渲染,而无需通过链接进行手动跳转。通常,这种场景发生在我们希望在父组件渲染时,默认显示某个子组件的情况。

实现默认二级路由的方式:

我们可以通过在父路由的 children 中指定一个没有 path 的路由配置来实现默认二级路由。这样,当父路由被匹配时,默认会渲染这个没有 path 的子路由,作为默认的子组件。

实现步骤

  1. 在父路由的 children 配置中,加入一个没有 path 的路由。这一项路由配置会作为默认的二级路由。
  1. 使用 <Outlet /> 在父组件中渲染子路由。
  1. 在子路由中没有 path 的路由将会自动渲染为默认子组件。

示例

1. 路由配置

2. 父组件中使用 <Outlet />

3. 子组件(Profile 和 Settings)

4. 应用入口文件

关键点

  1. index: true:在嵌套路由配置中,index: true 表示该路由是默认的子路由。这意味着在父路由匹配时,会自动渲染该子路由,不需要任何路径。它会被渲染为 <Outlet /> 的默认组件。
    1. 在上面的例子中,Profile 组件作为 index: true 的路由,会在 /dashboard 被访问时默认渲染,而不是需要手动点击 Profile 链接。
  1. <Outlet />:这是一个占位符,用来渲染父路由的子路由。在父路由组件中使用 <Outlet />,并在子路由中渲染相应的组件。对于没有 path 的路由(即默认路由),它会在父路由第一次渲染时显示。

默认二级路由的场景

默认二级路由非常适合以下场景:
  • 仪表盘或管理后台:通常,父路由(如 /dashboard)进入时,希望默认显示某个子页面(如个人资料页),不需要用户点击导航项来加载。
  • 用户中心:当用户访问 /user 路径时,默认显示用户的资料信息,而不需要先跳转到 /user/profile

总结

  • index: true:标识该子路由为默认路由,当父路由匹配时会自动渲染。
  • <Outlet />:用于父组件中渲染子路由组件,默认子路由会在 <Outlet /> 中显示。
 

404路由配置

场景:当浏览器输入url的路径在整个路由配置中都找不到对应的path,为了用户体验,可以使用404兜底组件进行渲染。
React Router 提供了一个 * 路径(或 path="*")来匹配所有未被定义的路径,这样就可以实现 404 页面。当用户访问一个不存在的路径时,会触发这个 404 路由。

实现步骤

  1. 定义一个 404 页面组件,用于展示 "页面未找到" 或其他自定义的提示。

示例代码

1. 创建 404 页面组件

2. 配置路由并添加 404 路由

3. 应用入口文件

关键点解析

  1. path="*":这是 React Router 中的 "catch-all" 路由,表示它会匹配任何未定义的路径。当用户访问一个不存在的页面时,这个路由会被触发,通常用于展示 404 页面。
  1. NotFound 页面:在这个组件中,我们可以定义任意内容,常见的做法是展示一个 404 错误页面,提示用户页面没有找到。
  1. 路由配置顺序:React Router 会按照路由配置的顺序依次匹配路径。在这种情况下,确保 "*" 路由放在最后面,因为 React Router 会从上到下匹配路径,一旦找到了匹配的路由,就不会继续往下匹配。

路由重定向

除了通过 404 页面处理未知路由,还可以在 * 路由中通过 Navigate 组件进行重定向,例如将用户重定向到首页或其他指定页面。

示例:使用 Navigate 重定向

总结

  1. path="*":用于匹配所有没有定义的路径,是设置 404 路由的标准方式。
  1. NotFound 组件:当路径未匹配时,可以用一个 404 页面组件来展示错误信息。
  1. 路由顺序:确保 path="*" 放在所有路由的最后,以避免覆盖其他定义的路由。
  1. 重定向:可以使用 Navigate 组件来在未匹配的路由中实现重定向。
4o

两种路由模式

各个主流框架的路由常用的路由模式有两种,history模式和hash模式,ReactRouter分别由createBrowerRouter和createHashRouter函数负责创建。
在 React Router 中,常见的两种路由模式是 "Hash 路由""Browser 路由",它们的主要区别在于 URL 的结构和如何处理浏览器历史记录。
notion image

1. Browser 路由(HTML5 History API 路由)

Browser 路由(也叫 HTML5 路由)使用了现代浏览器的 History API 来处理 URL 的变化。它允许我们在浏览器地址栏中使用类似于 https://example.com/path/to/page 这样的纯粹路径。

特点:

  • URL 没有 # 符号:URL 路径就是你在浏览器地址栏中看到的路径。
  • 路径与实际文件相对应:这意味着我们在地址栏输入的路径与服务器上的路径应匹配。
  • 支持浏览器历史记录:用户可以使用浏览器的前进、后退按钮来进行导航,且不需要重新加载页面。
  • 需要服务端支持:因为浏览器路由模式是基于路径的,所以如果直接刷新页面或访问某个路径(比如 https://example.com/about),需要确保服务器能够正确地处理这个请求并返回应用的 index.html 文件。

使用方法:

适用场景:

  • 对于支持后端服务的 SPA(单页面应用),通常使用 Browser 路由模式。
  • 需要干净、没有 # 符号的 URL。

问题:

  • 如果用户直接在浏览器地址栏输入路径(例如 /about),会向服务器发起请求,这就要求服务器能够处理这个路径请求并返回应用的 HTML 文件。否则会导致 404 错误。

2. Hash 路由(哈希路由)

Hash 路由使用 URL 的哈希部分(即 # 符号后面的部分)来模拟不同的页面。例如:https://example.com/#/about。这不会改变浏览器的地址栏的主路径部分,而是改变 # 后面的内容。

特点:

  • URL 包含 #:路径会带有 # 符号,后面跟着路由的路径。
  • 不需要服务器支持:哈希路由不会与服务器发生交互,它只会修改浏览器的哈希值,不会发送请求到服务器。因此,无论刷新页面,服务器都不会受到影响。
  • 不影响浏览器历史记录:浏览器依然可以记住路由的变化,并支持后退和前进按钮,但哈希部分不会导致浏览器重新加载页面。

使用方法:

适用场景:

  • 如果应用部署在静态资源服务器上,没有后端支持或者后端无法配置 URL 重写规则时,使用 Hash 路由是一种比较常见的选择。
  • 当应用需要运行在不支持服务器配置的环境下,比如 GitHub Pages,或者不希望为每个路由配置服务器的重定向时,使用 Hash 路由会更加方便。

问题:

  • URL 中包含 # 符号,虽然这不影响应用的运行,但可能让用户体验不如 Browser 路由。

对比

特性
Browser 路由
Hash 路由
URL 样式
https://example.com/about
https://example.com/#/about
服务器支持
需要配置,服务器必须正确处理路径
无需服务器支持,路径不会向服务器请求
浏览器刷新
刷新时需要服务器支持返回应用的 index.html
刷新不会影响,直接加载应用
历史记录支持
支持,使用浏览器的历史 API(前进、后退按钮)
支持,浏览器的历史记录会基于哈希值变化
路径匹配
直接匹配实际路径,如 /about
使用 # 后面的路径,如 #/about

总结

  1. Browser 路由
      • URL 清晰干净,没有 # 符号。
      • 需要服务器支持,适合传统的有后端支持的单页面应用(SPA)。
      • 可以处理基于路径的路由,支持现代浏览器的历史记录 API。
  1. Hash 路由
      • URL 包含 # 符号。
      • 不需要服务器支持,适合没有后端或者无法配置路由的环境。
      • 使用简单,不依赖服务器配置,但不如 Browser 路由那样清晰优雅。
两者选择的关键在于应用的部署环境。如果有完整的服务器支持,通常选择 Browser 路由,因为它可以提供更简洁的 URL 结构;如果是在没有后端路由配置的情况下,或者应用部署在静态资源服务器上,则可以选择 Hash 路由。

记账本demo

环境搭建

使用CRA创建项目,并安装必要依赖,包括下列基础包:
  1. Redux状态管理: @reduxjs/tookit、react-redux
  1. 路由:react-router-dom
  1. 时间处理:dayjs
  1. class类名处理:classnames
  1. 移动端组件库:antd-mobile
  1. 请求插件:axios
  1. scss:是一种后缀名为.scss的预编译CSS语言,支持一些原生CSS不支持的高级用法,比如变量使用,嵌套语法等,使用SCSS可以让样式代码更加高效灵活
    1. 安装的方式npm install sass -D
目录:
notion image

配置别名路径

  1. 路径解析配置(webpack),把@/解析为src/ (在不同的构建工具和框架中实现不一样)[解决的是编译解析]
    1. CRA本身把webpack配置包装到了黑盒里无法直接修改,需要借助一个插件 -craco。
      1. 安装craco: npm i -D @craco/craco
      2. 项目根目录下创建配置未见:craco.config.js
      3. 配置文件中添加路径解析配置
      4. 包文件中配置启动和打包命令
      5. notion image
  1. 路径联想配置(VsCode),VsCode在输入@/[路径联想]
    1. VsCode的联想配置,需要我们在项目目录下添加jsconfig.json文件,加入配置之后VsCode会自动读取配置帮助我们自动联想提示。
      1. 根目录下新增配置文件:jsconfig.json
      2. 添加路径提示配置
notion image

数据mock

在前后端分离的开发模式下,前端可以在没有实际后端接口的支持下先进行接口数据的模拟,进行正常的业务开发功能。
常见的mock服务
notion image
json-server实现数据Mock
json-server是一个node包,可以在不到30秒内获得零编码的完整的Mock服务。
  1. 项目中安装json-server:npm i -D json-server@0.17.3
  1. 准备一个json文件(和src同级,创建一个server目录,创建一个data.json)
  1. 添加启动命令
    1. notion image
  1. 访问接口进行测试
    1. notion image
补充说明
可以配置npm run start 执行的时候同时把mock数据服务一起启动,这样就可以执行两个命令
notion image

项目开发

  1. 整体路由设计,根据UI和交互分析
  1. antD-mobile主题定制,具体看官方文档
    1. notion image
  1. 定义store
    1. 异步请求获取的数据,例如billState
      1. notion image
  1. 具体设计分析,看文档→找相似的demo→复制代码跑通→定制化修改
    1. 使用antD的TabBar标签栏进行布局以及路由的切换。
      1. notion image
      5. 计算选择月份的数据统计
      notion image
      useMemo用法
      React中,useMemo 是一个用于性能优化的钩子函数。它允许你记住计算结果,以避免在每次渲染时重新计算某些复杂、开销大的数据。以下是它的基本用法和使用场景。

      基本用法

      useMemo 的语法如下:
      • computeExpensiveValue 是一个计算函数,只会在依赖项 [a, b] 发生变化时重新执行。
      • memoizedValue 是记住的计算结果,当依赖项未变时,它会直接返回上次的计算结果,从而避免重复计算。

      参数解释

      • useMemo 接收两个参数:
        • 第一个参数是一个计算函数,返回需要缓存的值。
        • 第二个参数是一个依赖项数组,只有在其中的变量发生变化时,useMemo 才会重新计算。

      使用场景

      1. 避免高计算量的重复执行:如果一个计算很耗时,比如大数据量的排序、过滤等,可以用 useMemo 来记住计算结果,减少开销。
      1. 优化子组件:如果一个子组件只依赖于某些值,那么使用 useMemo 可以确保传递给子组件的属性不会在不必要的渲染时更改。

      需求实现代码

    2. 登录-token持久化
      1. notion image
        notion image
        除此之外还可以用sessionStorage会话级别存储或者redux-persist插件.
        对于token的处理可以抽到util中进行方法复用.
    3. axios请求拦截器注入token
      1. Token作为用户的一个标识数据,后端很多接口都会以它作为接口权限判断的依据;请求拦截器注入Token之后,所有用到Axios实例的接口请求都自动携带了Token
        notion image
        请求相关的逻辑我们一般都封装在utils/request.js中
        说明
        • token 必须传递token 始终通过拦截器从 getToken 获取并添加到请求头。
        • userId 可选:如果需要 userId,可以通过各接口的参数传递。例如,在 getUserInfo 中,userId 作为查询参数传递。
        • Bearer ${token} 是一种常见的 HTTP 认证方式,通常用于在请求头中传递访问令牌(token)进行身份验证。
          • 具体来说:
          • Bearer:这是认证方案的类型,它表明该请求使用的是 "Bearer token"(承载令牌)。Bearer 是一种认证方式的标准术语,意味着后面跟着的是访问令牌。它告诉服务器请求者需要提供令牌来进行身份验证。
          • ${token}:这是 JavaScript 的模板字符串语法,用于将变量 token 的值插入到字符串中。例如,如果 token 的值是 abc123xyz,那么 Bearer ${token} 就会变成 Bearer abc123xyz
    4. 使用token做路由权限控制
    5.