05-React路由(Router 5版本)

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了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

风光互补路灯简介及配置方案

风光互补路灯是一种新型的路灯系统&#xff0c;使用太阳能和风能进行发电&#xff0c;以供给路灯照明。它结合了太阳能光伏板和垂直轴风力发电机&#xff0c;可以在一天内不同的时间段和天气条件下&#xff0c;通过自然资源发电&#xff0c;实现能源的自给自足和环境保护。 风…

鸿蒙系统扫盲(三):鸿蒙开发用什么语言?

1.两种开发方向 我们常说鸿蒙开发&#xff0c;但是其实鸿蒙开发分为两个方向&#xff1a; 一个是系统级别的开发&#xff0c;比如驱动&#xff0c;内核和框架层的开发&#xff0c;这种开发以C/C为主 还有一个是应用级别的开发&#xff0c;在API7以及以下&#xff0c;还是支持…

attention中Q,K,V的理解

第一种 1.首先定义三个线性变换矩阵&#xff0c;query&#xff0c;key&#xff0c;value&#xff1a; class BertSelfAttention(nn.Module):self.query nn.Linear(config.hidden_size, self.all_head_size) # 输入768&#xff0c; 输出768self.key nn.Linear(config.hidde…

Open3D 最小二乘拟合二维直线(直接求解法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重。 一、算法原理 平面直线的表达式为: y = k x + b

台灯选什么样的对眼睛好?分享适合备考使用的护眼台灯

随着生活条件的逐渐提升&#xff0c;台灯的需求也越来越大&#xff0c;不管在生活中还是工作中&#xff0c;台灯成为了必不可少的照明工具。而且现在很多孩子都是存在视力问题的&#xff0c;这也让不少家长纷纷开始重视起来&#xff0c;为了更好的保护孩子的眼睛选择更为专业的…

数据探索:五款免费数据可视化工具概览

数据可视化是解读和传达数据的重要方式&#xff0c;而现在有许多免费的工具可供选择&#xff0c;让您在探索数据时更轻松、更有趣。以下是五款推荐的免费数据可视化工具&#xff1a; Tableau Public&#xff1a; Tableau Public是一款功能强大的可视化工具&#xff0c;能够创建…

SpringBoot项目打成jar包后,上传的静态资源(图片等)如何存储和访问

1.问题描述&#xff1a; 使用springboot开发一个项目&#xff0c;开发文件上传的时候&#xff0c;通常会将上传的文件存储到资源目录下的static里面&#xff0c;然后在本地测试上传文件功能没有问题&#xff0c;但是将项目打成jar包放到服务器上运行的时候就会报错&#xff0c…

指标管理必知的真相:订单事实表里没有原子指标

上篇文章「一个问题鉴定指标管理真实力&#xff1a;订单表里有原子指标吗&#xff1f;」写完以后&#xff0c;分享到一个群里&#xff0c;有同行回复&#xff1a;有&#xff0c;比如订单金额。 看到回复&#xff0c;我脑子突然就断电了&#xff0c;好像对诶&#xff01;确实是…

借助 DPM 代码扫描的力量解锁医疗设备的可追溯性

在当今的医疗保健系统中&#xff0c;医疗设备的可追溯性变得比以往任何时候都更加重要。为了增强现代医疗保健领域的可追溯性和安全性&#xff0c;UDI 条形码充当唯一设备标识的标准&#xff0c;为医疗设备提供唯一标识符。 DataMatrix 代码&#xff08;或直接零件标记代码&am…

Python爬取某电商平台商品数据及评论!

目录 前言 主要内容 1. 爬取商品列表数据 2. 爬取单个商品页面的数据 3. 爬取评论数据 4. 使用代理ip 总结 前言 随着互联网的发展&#xff0c;电商平台的出现让我们的消费更加便利&#xff0c;消费者可以在家里轻松地购买到各种商品。但有时候我们需要大量的商品数据进…

网络和Linux网络_7(传输层)UDP和TCP协议(端口号+确认应答+超时重传+三次握手四次挥手)

目录 1. 重看端口号 1.1 端口号的概念 1.2 端口号的划分 2. 重看UDP协议 2.1 UDP协议格式 2.2 UDP的特点 3. 重看TCP协议 3.1 TCP协议格式 3.2 TCP的解包分用 3.3 TCP的可靠性及机制 3.3.1 确认应答ACK机制 3.3.2 超时重传机制 3.3.3 连接管理机制&#xff08;三次…

内存泄漏检测工具valgrind

示例&#xff1a; class Person { public:Person(int age){//将年龄数据开辟到堆区m_Age new int(age);}//重载赋值运算符 写法2 此代码在linux测试Person& operator(Person& p){*m_Age *p.m_Age; //通过linux下valgrind工具检测&#xff0c;无内存泄漏情况。//此语…

LVS+Keepalived实验

实验前准备 主DR服务器&#xff1a;(ens33)192.168.188.11 ipvsadm、keepalived (ens33:0)192.168.188.188 备DR服务器&#xff1a;(ens33)192.168.188.12 ipvsadm、keepalived (ens33:0)192.168.188.188 Wbe服务器1&#xff1a;(ens33)192.168.188.13 (lo:0)192.168.188.188 W…

UG\NX二次开发 获取对象上属性的锁定状态UF_ATTR_ask_locked

文章作者&#xff1a;里海 来源网站&#xff1a;里海NX二次开发3000例专栏 感谢粉丝订阅 感谢 2301_80435318 开发 订阅本专栏&#xff0c;非常感谢。 简介 设置对象上属性的锁定状态UF_ATTR_set_locked&#xff0c;需要先在“用户默认设置”中勾选“通过NX Open锁定属性”&…

【攻防世界-misc】[简单] 凯撒大帝在培根里藏了什么

1.下载文件&#xff0c;打开后是这样子的 2.根据题目提示说是有凯撒密码和培根密码&#xff0c;因为文件内容为AB形式&#xff0c;不符合凯撒条件&#xff0c;所以先用培根解&#xff0c;将文件内容复制&#xff0c;CTF在线工具-CTF工具|CTF编码|CTF密码学|CTF加解密|程序员工具…

虚拟机安装centos7系统后网络配置

一.桥接网络和nat网络的区别1&#xff0c;桥接模式&#xff08;如果外部访问虚拟机&#xff0c;最好选这个&#xff09; 通过使用物理机网卡 具有单独ip,但是需要手动配置。 在bridged模式下&#xff0c;VMWare虚拟出来的操作系统就像是局域网中的一台独立的主机&#xff0c;它…

数字孪生智慧园区:企业与政府合作的共赢之选

随着科技的快速发展和数字化转型的推动&#xff0c;数字孪生技术正逐渐成为智慧城市和园区建设的重要方向。数字孪生智慧园区&#xff0c;以数字孪生技术为驱动&#xff0c;通过对园区实体和虚拟环境的全面感知和深度理解&#xff0c;为园区管理者和入驻企业提供智能化决策支持…

2023 BUCT 计算方法实验报告

前言 Textlive版本&#xff1a;2023 textstudio版本&#xff1a;4.6.3 名字和日期在以下地方修改: 图片下载地址; figures.zip LiangCha_Xyy/Source - Gitee.com 如下图&#xff0c;.tex文件和figures文件夹放在同一路径下即可 .tex代码 \documentclass[UTF8]{ctexart} \usep…

行业研究:2023年建筑涂料市场需求及发展方向分析

目前绿色化经济成为社会经济主旋律&#xff0c;涂料行业作为高污染行业&#xff0c;国家层面出台了一系列政策规划引导行业向绿色、安全、环保发展。例如&#xff0c;禁止使用高VOCs含量的溶剂型涂料&#xff0c;推广水性涂料等低VOCs含量涂料&#xff0c;鼓励外商投资高性能涂…

电力变压器行业分析:预计2029年将达到84亿元

随着中国“节能降耗”政策的不断深入&#xff0c;国家鼓励发展节能型、低噪音、智能化的配电变压器产品。在网运行的部分高能耗配电变压器已不符合行业发展趋势&#xff0c;面临着技术升级、更新换代的需求&#xff0c;未来将逐步被节能、节材、环保、低噪音的变压器所取代。 电…