05-React路由(Router 5版本)

news2024/12/27 12:47:53

React路由背景介绍

背景介绍

多页面应用与SPA单页面应用

多页面应用
先说传统的多页面,需要写多个子页面
在这里插入图片描述
点击导航栏,整个页面都会刷新,但是实际上我只想刷新一小块的内容,其他东西变化不大

而且这个单页面,每次切换完还需要更新一点内容
就像导航栏,每次跳转完后,href地址也要有小幅变化
但是大部分东西是相似的,这资源利用太差了
所以就有了SPA应用

在这里插入图片描述
SPA应用
单页Web应用(single page web application,SPA)。
整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的局部更新。
数据都需要通过ajax请求获取, 并在前端异步展现。
单页面、多组件

SPA应用的实现就需要依靠于路由

它比传统的 Web 应用程序更快,因为它们在 Web 浏览器本身而不是在服务器上执行逻辑。在初始页面加载后,只有数据来回发送,而不是整个 HTML,这会降低带宽。它们可以独立请求标记和数据,并直接在浏览器中呈现页面,这就是SPA的优势,可以很好的节约带宽以及开发组件成本。

对于路由的理解

什么是路由?
一个路由就是一个映射关系(key:value)
key为路径, value可能是function(运行某个函数)或component(请求某个组件)

路由为前端路由和后端路由

前端路由:

  1. 浏览器端路由,value是component,用于展示页面内容。
  2. 注册路由: <Route path="/test" component={Test}>
  3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
  4. 前端路由也要靠BOM 上的 history

后端路由:

  1. 理解: value是function, 用来处理客户端提交的请求。
  2. 注册路由: router.get(path, function(req, res){})
  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

关于router里面的value是function的解释,触发url之后就需要走这个function
在这里插入图片描述

路由实现的基础原理

这部分必须了解,但是不需要实操

前端路由的实现主要依靠于BOM对象的history属性,也就是浏览器历史记录
其次,用浏览器的历史记录是用栈保存的,所以有(LIFO 后进先出)

BOM(Browser Object Model) 是指浏览器对象模型,下属有这几个属性可以操作
		window对象 ,是JS的最顶层对象,其他的BOM对象都是window对象的属性
		location对象,浏览器当前URL信息	
		navigator对象,浏览器本身信息
		screen对象,客户端屏幕信息	
		history对象,浏览器访问历史信息	
		
DOM(Document Object Model),网页文档对象模型,这个在这里不算重点就不多展开了

首先可以通过API对BOM对象进行操作,比如这样:

let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API

但是这样操作不太好,所以有一个其他的JS库可以帮忙实现操作DOM
引入js代码

<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>

这里放几个操作

在 H5 中新增了 createBrowserHistory 的 API ,用于创建一个 history 栈,允许我们手动操作浏览器的历史记录

新增 API:pushStatereplaceState,原理类似于 Hash 实现。 用 H5 实现,单页路由的 URL 不会多出一个 # 号,这样会更加的美观

<a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1</a><br><br>
<button onClick="push('/test2')">push test2</button><br><br>
<button onClick="replace('/test3')">replace test3</button><br><br>
<button onClick="back()">&lt;= 回退</button>
<button onClick="forword()">前进 =&gt;</button>

<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>

<script type="text/javascript">
	// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
	let history = History.createHashHistory() //方法二,hash值(锚点)

	function push (path) {
		// 页面跳转,栈中添加一条记录
		history.push(path)
		// return false就无法进行跳转,只能发现url发生了变化。true的话就可以跳转了
		return false
	}
		
	function replace (path) {
		// 页面跳转,但是栈中不会添加一条记录,而是替换当前的记录
		history.replace(path)
	}

	function back() {
		// 相当于浏览器回退按钮按下
		history.goBack()
	}

	function forword() {
		// 相当于浏览器前进按钮按下
		history.goForward()
	}

	history.listen((location) => {
		// 监听导航栏url变化
		console.log('请求路由路径变化了', location)
	})
</script>

以上就是前端路由所依靠的技术基础

React Router5路由(已过时)

ReactRouter5版本,已经过时,现已经有router6版本。
这里学习的目的是温故知新,为后面衔接router6做准备

版本选择

非常重要的一个知识,项目里开发用到的是react-router-domreact-router-dom分了不同的版本实现,是给不同平台去用的

  • 网页应用 web (用这个)
  • React native(原生应用)
  • 通用 Anywhere

路由:是路由器上的一根根天线
路由器:是用来管理路由的,也就管理那一根根天线的
,我们自己买一大堆天线路由,没有路由器把他管理起来,就是不能用的。这里监听url变化的就是前端路由器

安装

路由-DOM(5版本)

// npm安装路由
npm i react-router-dom@5
// 查看版本
npm react-router-dom -v

在这里插入图片描述

路由的基本使用

react-router-dom 专门给 web 人员使用的库

  1. 一个 react 的仓库
  2. 很常用,基本是每个应用都会使用的这个库
  3. 专门来实现 SPA 应用

首先我们要明确好页面的布局 ,分好导航区、展示区。分区完毕之后再加路由等等…

要引入 react-router-dom 库,暴露一些属性 Link(相当于之前的a标签)、BrowserRouter(路由器包裹标签)...

import { Link, Route, BrowserRouter } from 'react-router-dom'

Link标签

导航区的 a 标签改为 Link 标签

// 这里只放对比
  <div className="list-group">
    {/* 原生是靠a标签跳转,我们要用Link 进行替换 */}
     <a className="list-group-item" href="./about.html">
      About
    </a>
    <Link className="list-group-item" to="/about">
      About
    </Link>
  </div>

Link标签会让url变化,只要url变化,就会被路由器监听到,发现变化进行组件跳转
(这里隐藏了Route标签包裹的问题,下面马上就会提到)
在这里插入图片描述


Route(路由)标签

import Home from "./componment/routerDemo/home/Home";
import About from "./componment/routerDemo/about/About";

<div className="panel-body">
   {/* 注册路由 */}
   <Route path="/about" component={About}></Route>
   <Route path="/home" component={Home}></Route>
</div>

我们需要用 Route 标签,来进行路径的匹配(访问到了这个url就加载这个路由),从而实现不同路径的组件切换。
在这里插入图片描述

点击别的Link这块的组件就会发生变化
在这里插入图片描述

Router(路由器)标签

Router是一个大类,类似Java的接口,下属两个标签:<BrowserRouter><HashRouter>

router除了做组件匹配,还有一个非常重要的功能,就是做路由的路由器(管理)。他就像一个接口,下面有两个不同的实现路由器
两个路由器最大的区别就是url上面,至于二者的来源,就是之前提到的浏览器BOM的history对象

  • <BrowserRouter>
    对应的url就是http://localhost:3000/about,没有#

  • <HashRouter>
    对应的url就是http://localhost:3000/about#/about,有#

所以不难发现,对于用户来说,<BrowserRouter> 没有#号的体验最好

之前的代码一直是给<BrowserRouter>包裹路由器问题忽略了,实际上如果不包,运行代码,此时就会发现,Link外面需要有路由器标签包裹(这里指的子标签BrowserRouter或HashRouter,用Router标签包是不行的)
在这里插入图片描述

因此我们也可以在 Link 和 Route 标签的外层标签采用 BrowserRouter 包裹,但是这样当我们的路由过多时,我们要不停的更改标签包裹的位置,因此我们可以这么做,找到最顶层标签然后用路由器标签包裹(<BrowserRouter><HashRouter>

我们回到 App.jsx 目录下的 index.js 文件,将整个 App 组件标签采用 BrowserRouter 标签去包裹,这样整个 App 组件都在一个路由器的管理下,

// index.js
<BrowserRouter>
< App />
</BrowserRouter>
Router(路由器)的反例

反例就是把Link和Route放在不同的路由器下面,导致两个路由器不互通,就失去了路由的意义
在这里插入图片描述
在这里插入图片描述

路由组件和普通组件的区别

1.写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={需要导入的组件}/>

为了规范我们的书写,一般将路由组件放在 pages 文件夹中,路由组件放在 components
我们指的路由组件,是在<Route/> 这个标签里面component导入的标签(也就是匹配url变动的标签)

在这里插入图片描述

2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性(history,location,match)
在这里插入图片描述
运行代码输出一下,可以看到路由组件默认会有非常多的参数,但是对于开发人员来说只用到一部分

在普通组件中,如果我们不进行传递,就不会收到值。而对于路由组件而言,它会接收到 3 个固定属性 historylocation 以及 match
在这里插入图片描述
路由组件里常用的参数,这些在后面的编程式路由导航有很大用:

history:
			go: ƒ go(n)
			goBack: ƒ goBack()
			goForward: ƒ goForward()
			push: ƒ push(path, state)
			replace: ƒ replace(path, state)
location:
			pathname: "/about"
			search: ""
			state: undefined
match:
			params: {}
			path: "/about"
			url: "/about"

NavLink

NavLink 标签是和 Link 标签作用相同的,但是它又比 Link 更加强大。

在前面的 demo 展示中,你可能会发现点击的按钮并没有出现高亮的效果,正常情况下我们给标签多添加一个 active 的类就可以实现高亮的效果

而 NavLink 标签正可以帮助我们实现这一步

当我们选中某个 NavLink 标签时,就会自动的在类上添加一个 active 属性

在这里插入图片描述
activeClassName属性

我们可以看到左侧的元素类名在不断的切换,当然 NavLink 标签是默认的添加上 active 类,我们也可以改变它,在标签上添加一个属性 activeClassName

例如 activeClassName="自定义css" 在触发这个 NavLink 时(点击了),会自动添加一个 自定义css

在css文件里加入的属性

.btn-active {
  color: #fff;
  background-color: rgb(47, 189, 71); # 绿的
}

jsx的NaviLink

import "./App.css";
<NavLink
  className="list-group-item"
  activeClassName="btn-active !important"
  to="/about"
>
  {/* className 是未被选中的状态下的css   
      activeClassName 是被选中状态下的css  加!important是为了提升css优先级*/}
  About
</NavLink>

在这里插入图片描述

NavLink二次封装

在上面的 NavLink 标签中,我们可以发现我们每次都需要重复的去写这些样式名称或者是 activeClassName ,这并不是一个很好的情况,代码过于冗余。再比如我们想封装一点自己的属性进去,就可以对NavLink进行二次封装,搞一个自定义的标签。

我们可以自定义一个 MyNavLink 组件,对 NavLink 进行封装

比如这样:

<MyNavlink to="/about">About</MyNavlink>
等价于
<MyNavlink to="/about" children={"About"} />

注意,标签体内容也是一个特殊的标签属性

<MyNavlink to="/about">About</MyNavlink>
这里面夹着的About属性
是标签体内部的值,是可以作为props属性传入的

如果是自闭合标签,不直接传值,用children属性传效果是一样的
<MyNavlink to="/about" children={"About"} />

比如我在自定义组件内部打印一下,就能看到这个值
在这里插入图片描述
固定字段children属性就保存着我们的标签体内容
在这里插入图片描述


尝试给自定义的NavLink里传入大量参数
调用方

<MyNavlink to="/about" a={1} b={2} c={"c"}>
  About
</MyNavlink>

自定义标签方
这个时候就会有个问题,像这样用解构去挨个接收参数很麻烦

const { a, b, c } = this.props;

所以咱们直接不解构了,直接传进去的

上面是调用传入的参数
<MyNavlink to="/about" a={1} b={2} c={"c"}>
  About
</MyNavlink>

这里用{...this.props} 给上面的[to="/about" a={1} b={2} c={"c"}]这些
全部给扒过来,连带key和value一起给带到NavLink里面(包括那个标签体内的About)

//这里一定要用展开符展开一下,要不然解不开报错
<NavLink className="list-group-item" {...this.props} />

在里面打印下,看一下传入效果,全部k-v都传过来了
在这里插入图片描述

Switch标签解决同路径匹配问题

<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>
<Route path="/home" component={Test}></Route>

这是两个路由组件,在2,3行中,我们同时使用了相同的路径 /home

匹配时,就会出现两个组件同时显示的情况(这两个组件如果想一起显示明明可以放在一个组件里面)
在这里插入图片描述
所以,这个案例就说明,路由做匹配是匹配所有的Route标签,如果是大项目里面一大堆的Route标签,挨个匹配就会降低性能
所以引出Switch标签

匹配到了之后,后面的就不会再匹配了,从而节约性能

import { Switch } from "react-router-dom";

<Switch>
  <Route path="/about" component={About}></Route>
  <Route path="/home" component={Home}></Route>
  <Route path="/home" component={Test}></Route>
</Switch>

在这里插入图片描述

解决二级路由样式丢失的问题

之前的路径一直是http://localhost:3000/home这种的一级路径
那如果是二级路径http://localhost:3000/test/home这种的二级路径就会造成样式丢失的问题


其根本原因,是因为css文件在脚手架的public目录下,写成了相对路径,当路径发生变化的时候,因为是相对路径,所以前缀就会带上test的路径,导致找不到文件而返回默认的index.html

在这里插入图片描述

在这里插入图片描述

解决方案

将样式引入的路径改成绝对路径,不带 ./
css文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>react脚手架</title>
    //绝对路径
    <link rel="icon" href="/favicon.ico" />
    <link rel="stylesheet" href="/css/bootstrap.css" />
  </head>
  ...
</html>

在这里插入图片描述

引入样式文件时用%PUBLIC_URL%
%PUBLIC_URL%代表脚手架路径,就是http://localhost:3000/test/home里面的http://localhost:3000

所以这个就是绝对路径,就不会再丢失了
在这里插入图片描述

使用 HashRouter (不常用,用户体验不好)

http://localhost:3000/#/test/home #号后面的内容都被认为是前端资源,根本不带给服务器。#号后面的路径就自动忽略了,所以就不存在丢失问题

此时的css无论是相对路径或者是绝对路径就都无所谓了
css:

<link rel="icon" href="./favicon.ico" />
<link rel="stylesheet" href="./css/bootstrap.css" />
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <HashRouter>
      <App />
    </HashRouter>
);

在这里插入图片描述

在实际开发中,一般常用绝对路径HashRouter很少用


路由的精准匹配和模糊匹配

路由的匹配有两种形式,一种是精准匹配一种是模糊匹配,React 中默认开启的是模糊匹配

  • 模糊匹配可以理解为,只要路径顺序能匹配上并且url范围比路由path大,就可以匹配上

  • 精准匹配就是,匹配路由时,两者必须相同

我们展示一个模糊匹配的例子

<NavLink className="list-group-item" to="/home/a/b">
  Home
</NavLink>
//路由path,只要是顺序能对上就可以触发路由,所以下面两个路由匹配[/home/a/b]都可以匹配上
<Route path="/home" component={Home}></Route>
<Route path="/home/a" component={Home}></Route>
//但是这种,超出了范围的就无法匹配了
<Route path="/home/a/b/c" component={Home}></Route>

简单来说,想要触发模糊匹配需要:
路径顺序能匹配 + url的范围小于Route的path范围 就可以触发。这么设计是为了后面react路由的匹配,所以开发中一般用的都是模糊匹配
在这里插入图片描述
无法模糊匹配的例子

<NavLink className="list-group-item" to="/home/a/b">
  Home
</NavLink>
//路由path
<Route path="/home" component={Home}></Route>
<Route path="/home/a" component={Home}></Route>
//这种超出了范围的就无法匹配
<Route path="/home/a/b/c" component={Home}></Route>
//这种顺序错了的也无法匹配
<Route path="/a/home/b" component={Home}></Route>

在这里插入图片描述
尝试开启精准路由

当我们开启了精准匹配后,就我们的第一种匹配就不会成功,因为精准匹配需要的是完全一样的值,开启精准匹配采用的是 exact 来实现,效果就不展示了,必须url完全匹配上路径才可以

<Route exact={true}  path="/home" component={Home}/>

请注意,精准匹配不要轻易开启,很容易造成路由嵌套无法匹配的问题,如果不是影响到项目开发,就不要开启精准路由

Redirect重定向路由

在我们写好了这些之后,我们会发现,我们需要点击任意一个按钮,才会去匹配一个组件,这并不是我们想要的,我们想要页面一加载上来,默认的就能匹配到一个组件。

这个时候我们就需要时候 Redirecrt 进行默认匹配了,属于兜底的方法

只要是没有匹配到 /about 或者 /home,就会默认去Redirect标签标记的home 路径

<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>
<Redirect to="/home" />

比如url输入http://localhost:3000/home1
或者是一进来页面的默认的http://localhost:3000/路径,都会被Redirect标签给路由到兜底到/home
也就是http://localhost:3000/home
在这里插入图片描述

嵌套路由

先看一个情景,home组件里面又套两个Link切换的组件,这种情况就需要用嵌套路由来解决

在这里插入图片描述

  • 一级路由:/home
  • 二级路由:/home/About
  • 三级路由:/home/About/Test
  • 多级路由:/.../.../.../.../...

其实简单来说,就是把想要展示的组件,用多级路由嵌套出来一个唯一url,来匹配这个组件

以下是App.jsx组件的内容,用NavLink来展示Home组件

class App extends React.Component {
  render() {
    return (
      <div>
         <div className="row">
            <NavLink className="list-group-item" to="/about">
              About
            </NavLink>
            <NavLink className="list-group-item" to="/home">
              Home
            </NavLink>
          </div>
          <div className="panel">
              <Route path="/about" component={About}></Route>
              <Route path="/home" component={Home}></Route>
          </div>
      </div>
    );
  }
}
export default App;

我们将我们的嵌套内容写在相应的组件里面,这个是在 Home 组件的 return 内容

<div>
    <h2>Home组件内容,</h2>
    <div>
        <ul className="nav nav-tabs">
            <li>
                <MyNavLink className="list-group-item" to="/home/news">News</MyNavLink>
            </li>
            <li>
                <MyNavLink className="list-group-item " to="/home/message">Message</MyNavLink>
            </li>
        </ul>
        {/* 注册路由 */}
        <Switch>
            <Route path="/home/news" component={News} />
            <Route path="/home/message" component={Message} />
        </Switch>
    </div>
</div>

这里提一下开发的包规范:Home组件里嵌套的News组件和Message组件,可以放在Home的包下面
在这里插入图片描述
或者用在同级别的包下,用父组件_子组件的包名也可以。总之各个公司不一样,可以根据实际情况再去探讨
在这里插入图片描述


在这里我们需要使用嵌套路由的方式,才能完成匹配

首先我们得 React 中路由得注册是有顺序的,在匹配得时候,因为 Home 组件是先注册得,因此在匹配的时候先去找 home 路由,由于是模糊匹配,会成功的匹配

在 Home 组件里面去匹配相应的路由,从而找到 /home/news 进行匹配,因此找到 News 组件,进行匹配渲染

如果开启精确匹配的话,第一步的 /home/news 匹配 /home 就会卡住不动。后面写的/home/news 这个时候就不会被匹配到了

当前标题下挤压太多东西了,这里给传参部分单独起一个大标题

路由参数传递(声明式路由导航)

param传递参数

可以在url里面传递参数,做出的效果就是点击开一个嵌套路由,就可以把参数从Link里面传给嵌套的路由。
react-router-params
这种参数传递就是依靠于url上拼接参数

我们可以通过将数据拼接在路由地址末尾来实现数据的传递

这里我们需要注意的是:需要采用模板字符串以及 $ 符的方式来进行数据的获取

 <Link to={`/home/message/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>

如上,我们将消息列表的 id 和 title 写在了路由地址后面


在注册路由时,可以在路由上的url通过 :数据名 来接收数据

<Route path="/home/message/:id/:title" component={Message} />

如上,使用了 :id/:title 成功的接收了Link 传递过来的 id 和 title 数据(从App组件,传递给Home组件下的子组件Message)

这样我们既成功的实现了路由的跳转,又将需要获取的数据传递给了 Detail 组件

我们在 Message组件中打印 this.props 来查看当前接收的数据情况

在这里插入图片描述

我们可以发现,我们传递的数据被接收到了对象的 match 属性下的 params 中

因此我们可以在 Message 组件中获取到传递来的 params 数据

并通过 params 数据中的 id 值,在详细内容的数据集中查找出指定 id 的详细内容,通过解构等方式来实现

const { id, title } = this.props.match.params;

最后直接使用渲染数据即可

总结一下优缺点:

  • 优点:刷新页面,参数不丢失
  • 缺点:1.只能传字符串,传值过多url会变得很长 2. 参数必须在路由上配置

search传递参数

我们还可以采用传递 search 参数的方法来实现

首先我们先确定数据传输的方式

我们先在 Link 中采用 ? 符号的方式来表示后面的为可用数据

<Link to={`/home/message/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>

采用 search 传递的方式,无需在 Route 中再次声明,因为是通过url的后缀进行传递的,类似这种:

https://csdn.net/home/message/?id=${msgObj.id}&title=${msgObj.title}

采用 search 传递的方式,无需在 Route标签中再和以前一样声明接收参数。转而在接受参数的组件中处理

打印一下参数我们可以看到,在location里面可以看到传入的参数(实际上就是url)
此时的url为:http://localhost:3000/home/message?id=id&title=title
在这里插入图片描述

那这个url里面的参数如果需要获取,就得借助工具来进行转换,这里选择了querystring
querystring引入时报错解决

import qs from "qs";

这个库是 React 中自带有的,它有两个方法,一个是 `parse` 一个是 `stringify` 
我们可以采用 `parse` 方法,将字符串转化为键值对形式的对象

// 解构获取location路径
const { search } = this.props.location;
const { id, title } = qs.parse(search.slice(1));

这样我们就能成功的获取数据,并进行渲染


其实以上两种都适用于对于数据并不敏感的场景,或者一些简单的数据传输,并不适合大规模的数据传输,并且因为参数是对外暴露在url的,所以并不是很安全。同时url会挂着一大堆的参数,会很长,这样不太好,由此就可以引出我们第三种数据的传递方式通过state进行传输。

总结一下优缺点:

  • 优点:刷新页面,参数不丢失
  • 缺点:1.只能传字符串,传值过多url会变得很长 2. 获取参数需要解构this.props

state传递参数

通过state进行参数传递,有效避免了数据暴露在url上的问题,采用内部的状态(state)来维护,在to里进行传递。

const obj = { id: "id", title: "title" };

<Link
  className="list-group-item"
  to={{
    pathname: "/home/message",
    state: { id: obj.id, title: obj.title }
  }}
>
  Home
</Link>
// route里面不再需要单独标记数据接收
<Route path="/home/message/" component={Message}></Route>

在这里插入图片描述
可以看到,数据已经通过this.props里的location.state传入组件,在组件中解构一下
直接解构完拿着用即可

const { id, title } = this.props.location.state;

如果传入的数据为空的话,解构就会出现undefined的情况

总结一下优缺点:

  • 优点:可以传对象
  • 缺点: 刷新页面,参数丢失

解决清除缓存造成报错的问题,可以在获取不到数据的时候用空对象来替代,例如,

const { id, title } = this.props.location.state || {}

当获取不到 state 时,则用空对象代替

这里的 state 和状态里的 state 有所不同

Push与Replace

Push

路由中,默认情况下,开启的是 push 模式,也就是说,每次点击跳转,都会向栈中压入一个新的地址,在点击返回时,可以返回到上一个打开的地址(所有的history全部记录)
react-router-push
就像上图一样,我们每次返回都会返回到上一次点击的地址中

Replace

有时候我们可能会不喜欢这种繁琐的跳转,我们可以开启 replace 模式,这种模式与 push 模式不同,它会将当前地址替换成点击的地址,也就是替换了新的栈顶。也就是无论切换了多少次url,因为是replace模式,所以history里面只有最新的这个url

我们只需要在需要开启的<Link/>上加上 replace 即可

<Link replace
  className="list-group-item"
  to={{
    pathname: "/home/message",
    state: { id: obj.id, title: obj.title },
  }}
>
  Home
</Link>

react-router-replace

编程式路由导航

React中,导航分为两种,声明式导航命令式导航

  • 声明式导航:之前我们直接写在代码里标记好的<Link/>标签,就是声明式的导航,只标明了一些属性,依靠属性来完成某些操作
  • 命令式导航:采用绑定事件的方式实现路由的跳转,我们在按钮上绑定一个 onClick 事件,当事件触发时,我们执行 replaceShow 函数,在方法体里通过操作this对象来改变url实现跳转操作this.props.history.replace(/home/message/${id}/${title})

之前的声明式导航,只有一个Link或者NavLink,自定义程度太低了,而且如果想搞其他功能只能二次封装。
但是用按钮完成跳转、定时任务跳转、点图片跳转…这些需求都没办法用声明式导航实现


如果想用编程式导航,就必须用路由组件
因为只有路由组件,props里才有这些编程式导航用的API来操作url,否则就没意义了

history:
			go: ƒ go(n)
			goBack: ƒ goBack()
			goForward: ƒ goForward()
			push: ƒ push(path, state)
			replace: ƒ replace(path, state)
location:
			pathname: "/about"
			search: ""
			state: undefined
match:
			params: {}
			path: "/about"
			url: "/about"

路由组件有两种

  • 一种是<Route path="/home/news" component={News}> Route标签component组件指定的
  • 另一种就是用React自带API withRouter(普通组件)来把普通组件处理成路由组件

例子:在路由组件里用编程式导航,点击就可以完成Link一样的效果

export default class News extends Component {
  // News是一个被Route标记了的路由组件!!!!!!!!
  moveToNextPage = () => {
    // 因为props里有history对象,所以就可以操作url
    this.props.history.replace(`/home/news/detail`);
    push,replace两种操作都可以修改url,区别就是回退时是否有记录
    this.props.history.push(`/home/news/detail`);
  };
  render() {
    return (
      <div>
        News
        <button onClick={this.moveToNextPage}>前往下一个组件</button>
        <Route path="/home/news/detail" component={Detail}></Route>
      </div>
    );
  }
}

反例:在普通组件里面使用history对象操作
这里不放太多代码,就是在App这个普通组件里打印一下props,结果就是什么都没有,更不用说后续操作history对象了

class App extends React.Component {
  render() {
    console.log(this.props);
	}
}

在这里插入图片描述

编程式路由导航传参

我们先看一下api
pushreplace这两个方法都是可以传state参数的,所以对比于传统的Link声明式导航传参,编程式导航依旧可以传参

history:
	go: ƒ go(n)
	goBack: ƒ goBack()
	goForward: ƒ goForward()
	push: ƒ push(path, state)
	replace: ƒ replace(path, state)

push和replace都是改变url的手段,区别就在于history的操作,例子就不纠结用哪个了,随便抓一个实现案例即可

编程式路由导航param传参

这个本质就是修改url进行的传参,方式和上面的param几乎完全一样,就是改了一下Link

class App extends React.Component {
  replaceShow = (id, title) => {
    // 这里注意用箭头函数向外扩散找this对象,否则会造成死循环
    // 通过变更url传参
    return () => this.props.history.push(`/home/message/${id}/${title}`);
  };

render() {
    const obj = { id: "id", title: "title" };
    return (
      <div>
        <div className="row">
          <Header></Header>
          <div className="col-xs-2 col-xs-offset-2">
          	// 传入参数
              <button onClick={this.replaceShow(obj.id, obj.title)}>
                命令式导航
              </button>
            </div>
          </div>
          // 路由注册参数
          <Route
            path="/home/message/:id/:title"
            component={Message}
          ></Route>
      </div>

    );
  }
}
export default App;

// 接收参数的组件直接解构即可
export default class Message extends Component {
  render() {
    // 解构获取location路径
    const { id, title } = this.props.match.params;

    console.log(id, title);

    return <div></div>;
  }
}

在这里插入图片描述

编程式路由导航search传参

class App extends React.Component {
  replaceShow = (id, title) => {
    // 这里注意用箭头函数向外扩散找this对象,否则会造成死循环
    // 通过变更url传参
    return () => this.props.history.push(`/home/message/?id=${id}&title=${title}`);
  };

render() {
    const obj = { id: "id", title: "title" };
    return (
      <div>
        <div className="row">
          <Header></Header>
          <div className="col-xs-2 col-xs-offset-2">
          	// 传入参数
              <button onClick={this.replaceShow(obj.id, obj.title)}>
                命令式导航
              </button>
            </div>
          </div>
          // 这里不再需要去注册参数了
          <Route
            path="/home/message"
            component={Message}
          ></Route>
      </div>

    );
  }
}
export default App;

// 接收参数的组件,qs查完直接解构即可
import qs from "qs";
export default class Message extends Component {
  render() {
    // 解构获取location路径
    const { search } = this.props.location;
    // 用qs给search传进来的参数查出来
    const { id, title } = qs.parse(search.slice(1));
    console.log(search);
    console.log(id, title);

    return <div></div>;
  }
}

从url里面解构出来挂着的参数,可以看到能够获取到并且打印出来
在这里插入图片描述

编程式路由导航state传参

看History下的这俩API,直接就能传state,直接往里面传值就可以了。不需要修改url和路由
和之前的state传值其实是一样的

history:
	push: ƒ push(path, state)
	replace: ƒ replace(path, state)
class App extends React.Component {
  replaceShow = (id, title) => {
    // 这里注意用箭头函数向外扩散找this对象,state传值不再需要修改url
    return () => this.props.history.push(`/home/message`, { "id": id, "title": title });
  };

render() {
    const obj = { id: "id", title: "title" };
    return (
      <div>
        <div className="row">
          <Header></Header>
          <div className="col-xs-2 col-xs-offset-2">
          	// 传入参数
              <button onClick={this.replaceShow(obj.id, obj.title)}>
                命令式导航
              </button>
            </div>
          </div>
          // 这里不再需要去注册参数了
          <Route
            path="/home/message"
            component={Message}
          ></Route>
      </div>

    );
  }
}
export default App;

export default class Message extends Component {
  render() {
    // 解构获取state里的数据
    const { id, title } = this.props.location.state;
    console.log(this.props.location);
    console.log(id, title);
    return <div></div>;
  }
}

在这里插入图片描述

编程式路由导航总结

借助this.props.history对象上的API对操作路由跳转、前进、后退
	go: ƒ go(n)  // n传入几就前进几步,传入正数前进,传入负数后退
	goBack: ƒ goBack()
	goForward: ƒ goForward()
	push: ƒ push(path, state)  // path是url路径,state是传值
	replace: ƒ replace(path, state) // path是url路径,state是传值

withRouter

当我们需要在页面内部添加回退前进等按钮时,由于这些组件我们一般通过一般组件的方式去编写,因此我们会遇到一个问题,无法获得 history 对象,这正是因为我们采用的是一般组件造成的。
普通组件去打印history对象是打印不出来的

class Header extends Component {
  // 这是个普通组件
  render() {
    // log一下参数
    console.log("普通组件输出参数", this.props.history);
    return <div>React-Router-Demo Header</div>;
  }
}

export default Header;

只有路由组件才能获取到 history 对象,普通组件是获取不到history对象,所以是个undefined
在这里插入图片描述


这里就可以用React路由自带的withRouter来对普通组件包装一下,使其变为路由组件

但是注意一个非常重要的东西,就是react-router-dom自带的withRouter首字母是小写!

在普通组件里面引入一下withRouter

import React, { Component } from "react";
// 引入withRouter
import { withRouter } from "react-router-dom";

class Header extends Component {
  // 这是个普通组件
  render() {
    // log一下参数
    console.log("普通组件输出参数", this.props.history);
    return <div>React-Router-Demo Header</div>;
  }
}

// 在最后导出对象时,用 `withRouter` 函数对 Header进行包装
export default withRouter(Header);

此时可以看到普通组件Header已经被改造成了路由组件

在这里插入图片描述

BrowserRouter 和 HashRouter 的区别

	1.底层原理不一样:
				BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
				HashRouter使用的是URL的哈希值。
	2.path表现形式不一样
				BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
				HashRouter的路径包含#,例如:localhost:3000/#/demo/test
	3.刷新后对路由state参数的影响
				(1).BrowserRouter没有任何影响,因为state保存在history对象中。
				(2).HashRouter刷新后会导致路由state参数的丢失!!!
	4.备注:HashRouter可以用于解决一些路径错误相关的问题。

为什么HashRouter刷新之后就会丢失state?

当使用 HashRouter 时,刷新页面可能会导致丢失 state 的原因在于它的工作原理。每次页面刷新时,浏览器都会重新加载页面和相关的资源,包括 JavaScript 代码和静态资源。由于 HashRouter 是通过 JavaScript 来处理路由的,因此在刷新页面时,JavaScript 代码需要重新加载,而之前保存的状态也会被清除。history丢失了,自然也就没有state了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1271579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】文件I/O-文件内容操作-输入输出流-Reader/Writer/InputStream/OutputStream四种流

导读 在文件I/O这一节的知识里&#xff0c;对文件的操作主要分为两大类&#xff1a; ☑️针对文件系统进行的操作 ☑️针对文件内容进行的操作 上文已经讲了针对文件系统即File类的操作&#xff0c;这篇文章里博主就来带了解针对文件内容的操作&#xff0c;即输入输出流&am…

2023工作中遇到问题一

1、vue中模拟鼠标点击下拉框 <xxx-select ref"aaa" :value"form.serviceCharge" placeholder"请输入" add"(value) > getBasGoods(value)" :configInfo"configInfo" />vue中模拟鼠标点击 this.$refs.aaa.$el.cli…

『C++成长记』构造函数和析构函数

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、类的六个个默认成员函数 &#x1f4d2;1.1认识默认成员函数 二、构造函数 …

【Linux】SELinux 关闭

SELinux 最在安装国产数据库&#xff0c;遇到了无法远程访问的问题&#xff0c;定位了之后&#xff0c;需要关闭SELinux 。 在关闭它之前我们一起了解一下什么是SELinux &#xff1f; 什么是SELinux 安全增强式 Linux&#xff08;SELinux&#xff09;是一种强制访问控制的…

ElementPlusError: [ElPagination] 你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档

使用element table出现这个错误好几回了&#xff0c;今天把它记录一下&#xff0c;并把错误原因复盘一遍。具体如下&#xff1a; 错误截图 原因 其实这个错误挺迷的&#xff0c;我把各种情况都测试了一遍&#xff0c;最后发现是因为给 翻页参数 total 传值错误导致的。 总结…

docker读取字体异常

解决方法 docker容器中执行 apk add ttf-freefont 根据版本不同 apk add ttf-dejavu-fonts apk add ttf-bernoulli

python 制作3d立体隐藏图

生成文件的3d图&#xff0c;例子&#xff1a; 文字&#xff1a; 隐藏图&#xff1a; 使用建议&#xff1a; &#xff11;、建议不用中文&#xff0c;因为中文太复杂&#xff0c;生成立体图效果不好。 &#xff12;、需要指定FONT_PATH&#xff0c;为一个ttf文件&#xff0c;…

Find My戒指|苹果Find My技术与戒指结合,智能防丢,全球定位

戒指是一种戴套在手指上做纪念或装饰用的小环&#xff0c;用金属、玉石等制成。如今智能戒指是炙手可热的可穿戴设备&#xff0c;智能戒指可以进行健康监测&#xff0c;包括实时监测心率、睡眠质量、步数等个人健康指标&#xff0c;并提供有关饮食和锻炼的建议&#xff0c;以帮…

自动化接口测试:Pytest让你轻松搞定!了解一般流程及方法

首先我们要明确&#xff0c;通常所接口测试其实就属于功能测试&#xff0c;主要校验接口是否实现预定的功能&#xff0c;虽然有些情况下可能还需要对接口进行性能测试、安全性测试。 在学习接口自动化测试之前&#xff0c;我们先来了解手工接口测试怎样进行。 URL组成 为了更…

代码随想录算法训练营 ---第五十一天

1.第一题&#xff1a; 简介&#xff1a; 本题相较于前几题状态复杂了起来&#xff0c;因为多了一个冷冻期。本题讲解可去代码随想录看&#xff0c;这里差不多只是加了些自己的理解。 动规五部曲&#xff0c;分析如下&#xff1a; 确定dp数组以及下标的含义 dp[i][j]&#x…

html5各行各业官网模板源码下载(1)

文章目录 1.来源2.源码模板2.1 HTML5白色简洁设计师网站模板 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134682321 html5各行各业官网模板源码下载&#xff0c;这个主题覆盖各行业的html官网模板&#xff0c;效果模…

用本子堆经验,手把手教你怎么写国自然项目基金!

随着社会经济发展和科技进步&#xff0c;基金项目对创新性的要求越来越高。申请人需要提出独特且有前瞻性的研究问题&#xff0c;具备突破性的科学思路和方法。因此&#xff0c;基金项目申请往往需要进行跨学科的技术融合。申请人需要与不同领域结合&#xff0c;形成多学科交叉…

一键分发平台-账号设置

首页-账号管理 ●登录后点击箭头-账号设置 控制台-账号管理 ●进入控制台-个人中心-账号管理 ●账号管理-个人资料介绍 ●账号管理-修改密码 ●账号管理-通知设置 ●账号管理-上传设置 ●账号管理-账号设置 ●账号管理-登录日志

数据结构与算法复习笔记

1.数据结构基本概念 数据结构: 它是研究计算机数据间关系&#xff0c;包括数据的逻辑结构和存储结构及其操作。 数据&#xff08;Data&#xff09;&#xff1a;数据即信息的载体&#xff0c;是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。 数据元素&#xf…

小程序禁止二次转发分享私密消息动态消息

第一种用法&#xff1a;私密消息 私密消息&#xff1a;运营人员分享小程序到个人或群之后&#xff0c;该消息只能在被分享者或被分享群内打开&#xff0c;不可以二次转发。 用途&#xff1a;主要用于不希望目标客群外的人员看到的分享信息&#xff0c;比如带有较高金额活动的…

【开源视频联动物联网平台】帧率、码率和分辨率

帧率、码率和分辨率是视频和图像处理中的重要概念&#xff0c;它们直接影响到视频的带宽占用和显示效果。在进行视频项目时&#xff0c;根据应用需求对视频参数进行调整是必要的&#xff0c;因此了解这些参数的具体含义和指标是非常重要的。 在进行视频项目时&#xff0c;需要…

软件系统安全漏洞检测应该怎么做?靠谱的软件安全检测公司推荐

软件系统安全漏洞检测是指通过对软件系统进行全面的、系统化的评估&#xff0c;发现和解决其中可能存在的安全漏洞和隐患。这些安全漏洞可能会被不法分子利用&#xff0c;引发数据泄露、系统瘫痪、信息被篡改等安全问题&#xff0c;给企业造成严重的经济和声誉损失。那么软件系…

万户ezOFFICE wpsservlet任意文件上传漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品&#xff0c;统一的基础管理平台&#xff0c;实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台&#xff0c;将外网信息…

【论文精读】HuggingGPT: Solving AI Tasks with ChatGPT and its Friends in Hugging Face

HuggingGPT: Solving AI Tasks with ChatGPT and its Friends in Hugging Face 前言Abstract1 Introduction2 Related Works3 HuggingGPT3.1 Task PlanningSpecification-based InstructionDemonstration-based Parsing 3.2 Model SelectionIn-context Task-model Assignment 3…

linux终端命令

comman [-options] [parameter] /主文件夹/视频 ls [-a -l -h] [Linux 路径] -a 全部文件显示 &#xff0c;因为有些是隐藏的文件 -l 以列表的形式展示内容&#xff0c;展示更加细节 -h 以易于阅读的形式&#xff0c;列出文件大小&#xff0c;如K、M、G 混合使用&#xff1a; l…