react 路由学了好久,断断续续的,也开学了。忙这忙那。 呼呼 。终于学完这一章节了 内容有点多,5.1 的相关知识只是浅谈,需要深入了解可以找找其他博文。如果这里有不对请指正啦。
第五章 — react-router
5.1 相关知识
1. 浅谈网页发展
1.1后端渲染阶段
早期网页
早期网页是由 jsp/php 代码开发,基本是html和css
它的请求过程是输入网站后,发送到服务器,服务器会将完整的网页返回,也就是已经渲染完成的网页,包括一些图片资源等。
这个也就是后端渲染,即请求回来的即使渲染完成的一个网页,只有html和css
当前端有什么请求时,后端会根据请求找到对于的页面,根据需求渲染完成后再发送给游览器。
后端路由
根据请求路径服务器会找到对于的网页,这个映射处理的过程就是后端路由的工作过程
即后端路由是处理URL和页面之间的映射关系的
1.2 前后端分离阶段
Ajax的出现,有了前后端分离的开发模式,后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JS渲染到页面上
服务器分为了静态服务器和一个提供API(接口)的服务器;
当游览器发送请求到服务器时,它先发送到静态服务器,静态服务器根据路径,返回HTML和CSS和JS的代码的静态网页;
如果JS代码发送请求,那么请求会发送到提供API的服务器;它会返回数据,JS根据数据处理并渲染到页面
这个过程就是前端渲染,因为大部分数据是是前端渲染出来的,后端只负责发送数据;
1.3 单页面富应用阶段(SPA):
SPA最主要的特点就是在前后端分离的基础上加了一层前端路由,也就是前端来维护一套路由规则
SPA页面
它只有一个HTML页面
它也有一个静态服务器,和一个提供API服务的服务器,相比前后端分离阶段,它的静态服务器里的保存的页面更少,甚至只有一个
当你发送一个请求,静态服务器返回一个有全部静态网页的的网页,这个网页里面还有其他页面(页面组件),但是会根据请求情况显示。
点击页面内的链接不会刷新页面,但会局部刷新。
数据通过 ajax 请求获取,并且在前端异步展现。
前端路由
一个主页面包括很多页面,当你点击到其他页面时,不会发送到静态服务器,而是在本地跳转,那么就需要一个本地路由映射关系,那就是前端路由
前端路由的关键是当点击到其他页面时,URL改变但是网页不刷新,在本地进行跳转
2. 路由
什么是路由
路由是一个映射关系。键是路径,值是一个组件。
路由的分类
路由可分为前端路由和后端路由。
前端路由和后端路由以上有介绍到。
5.2 前端路由的基本原理
前端路由跳转时不需要向服务器发送请求 ,而是本地进行跳转。
实现前端路由的核心要求是更新 URL 但是整体页面不刷新,只进行局部刷新。
而实现这个要求有几个方法。
location.hash
location 对象是 Window 对象的一个部分,可通过 window.location属性来访问。
location 对象有一些我们熟悉的属性。如 host ,href ,honstname 等。
而我们这里需讨论的是 hash 属性
hash 属性可以设置和返回 URL 后的哈希值 。(包括 # 号)
location.hash
location.hash = "C/D"
这个属性可以做到更新 URL ,但不会更新页面。
更新后可以返回更新前的 URL 。也就是之前的 URL 及页面状态会被会路由历史记录保留。
可以做一个尝试 ,点击网页 URL 栏, 可以修改URL ,在原本的 URL后加上#123 , 或 # 加其他字符 页面是不会刷新的。然后点击左边的回退键,是可以返回更新前的URL的。
哈希值还有一个特点,不会发送给服务器。
location.replace("…#…")
location 上有一个方法 ,是 replace 方法
参数是URL字符串 。 会把当前的URL在路由历史记录删除,并代签它的位置。
注意: 传入的URL必须是基于原本的 URL ,只增加或修改了哈希值。否则页面会刷新。
location.replace("www.abc.com/hwt.html#123")
history.length
history.pushStaet()
HTML5 的新方法
history.pushState() 方法向当前浏览器会话的历史堆栈中添加一个状态(state)。
它接受3个参数 pushState(state,title[,url])
-
state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。 -
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。 -
url:可选。新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
history.replaceState()
HTML5 的新方法 history.replaceState方法的参数与pushState方法一模一样,区别是它修改浏览历史中当前纪录 也就是说,它修改当前页面的路径,不刷新,但是之前的不会保存,无法返回;
history.go() / forward() / back()
back()
移动到上一个访问页面
forward()
移动到下一个访问过的页面
go()
接收一个整型参数,向前(正) 或向后访问
**如果访问的位置超过历史边界,并不会报错,而是失效 **
这些方法也可能达到更新 URL 但页面不会刷新 。
但使用这些方法的路由跳转必须是已经压入路由的历史记录堆栈里的 。
也就是有记录的 。否则是失效的。
5.3 react-router-dom
react 官方提供了一个 路由的库 。 react-router。
它包括三个部分 , web (专用于前端页面开发) , native ( react-native 使用) , anywhere(适应任何场景)
对应前端写网页,使用 web 的便可 。
基本使用
安装
npm add react-router-dom
引入
import { BrowserRouter, HashRouter,Link, Route, NavLink } from 'react-router-dom'
-
**Link 标签 : ** 用于跳转的链接标签 。它也一个 to 属性 ,值是 对应组件的路径(仅末尾部分)如 to="/home" -
NavLink 标签 : ** Link 标签的升级版 。 同样有一个 to 属性 。 但还多了一个 activeClassName 属性 ,值是这个标签处于被点击状态时的自定义类名**,默认 active 原生靠 a 标签跳转页面 ,react路由靠路链接 Link标签或NavLink 切换组件 , 但最后渲染成的是 a 标签。 -
**Route 标签 : ** route 标签是显示组件的 。 它与 <组件名/> 一样能显示标签。但只有处于改组件对应的路径时才会显示。 route 标签有两个属性 ,第一个是 path 属性 ,值与其对应的链接标签的 to 属性的值相等。一个是 component 属性 ,表示要显示的组件。如 component={Home} -
BrowserRouter 和 HashRouter 标签 这两个标签不需要什么属性,是用来包裹路由的其他标签的。否则其他路由标签无法工作。但不能放在不同的 …Router 标签内。 这两个标签选择一个即可。它们实现更改路由不刷新页面的方法不一样。但大致功能是一样的。 为了减少麻烦,可以直接放在 index.js 渲染的 标签外。 ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.querySelector("#root"));
示例 demo
import React, { Component } from 'react'
import './App.css'
// 引入两个组件 ,路由跳转的组件放在 src 下的 pages 文件夹下比较合适
import A from './pages/A/A'
import B from './pages/B/B'
// 引入路由相关的标签
import { BrowserRouter, Link, Route, NavLink } from 'react-router-dom'
export default class App extends Component {
render() {
return (
<div>
<BrowserRouter>
{/* 跳转链接区 */}
<NavLink activeClassName="act" className='list-item' to='/A'>A 组件</NavLink>
<NavLink activeClassName="act" className='list-item' to='/B'>B 组件</NavLink>
{/* to 表示转换到的路由,不需要完整的。 */}
{/* 显示区 */}
<Route path="/A" component={A} />
<Route path="/B" component={B} />
</BrowserRouter>
</div>
)
}
}
switch标签
react-router-dom 还暴露了一个 switch 标签 ,因为正常情况是一个路由一个组件,匹配到后就应该停止匹配。如果没有使用 switch 标签,那么每次匹配都会对所有 route 进行匹配。
如 :
<Route path="/home" component="Home" />
<Route path="/my" component="My" />
<Route path="/my" component="Other" />
匹配 /my 时会匹配到两个 ,最后即 显示 My组件 ,也显示 Other 组件
使用 switch 标签可避免这种情况发生 :
引入 import {Switch} from 'react-router-dom'
使用 switch 标签将所有 Route 标签包起来
<Switch>
<Route path="/home" component="Home" />
<Route path="/my" component="My" />
<Route path="/my" component="Other" />
</Switch>
匹配到 My 组件后,Other这个组件就不 会匹配了
路由组件与普通组件
他们本质都是组件 。 只是使用和存放有些不同。
-
普通组件即 <组件名/> 。 而路由组件是 <Route path="/路径" component={组件} /> -
普通组件放在 component 下 ,除App.jsx 。 路由组件放在 pages 下。 -
普通组件需要手动给其传递 props 。 而路由组件 router 会给组件的 props 传三个对象数据。 分别是 :
-
history -
location -
match 这几个对象数据在接下来的的教程会用到。
样式丢失问题
如果组件的路由使用了二级路由,即 /HHH/B 而不是 /B
然后我们在 index.html 里引入了其他样式,或者使用了相对路径引入其他文件
就可能出现加载失败的问题。
如 (index.html)
<link src='./src/somefile'></link>
组件内
<Route path="/HHH/home" component={Home} />
出错原因 :
第一次加载是正常路径,一般是成功的 ,当刷新时 , 因为 使用的是 ./src 。会在当前路径下回退。
如点击了组件链接后 , 路径变成 http://localhost:3000/HHH/A (localhost 前相当于
然后刷新 , ./ 会在当前路径回退一次,然后变成 http://localhost:3000/HHH/src/...
因此导致加载失败。
解决办法
- 去掉 ./ , 直接 /src/…
- 使用 %PUBLIC_URL% , 如 %PUBLIC_URL%/src/…
- 使用 HashRouter 。
模糊匹配和严格匹配
// 匹配成功
<Link to='/A/B/...' />
<Route path='/A' />
但如果是以下两种情况便不会成功
<Link to='/A' />
<Route path='/A/B/...' />
<Link to='/B/A/...' />
<Route path='/A' />
给 Route 增加 exact 属性,开启严格匹配。
如此只有 to 和 path 完全相等才会匹配成功
<Route exact ..../>
但严格匹配有可能导致二级路由匹配失败。一般使用
Redirect
在未使用 Redirect 时 ,第一次进入页面或刷新后 ,路由组件是不会显示的 。 因为当前路径只有基本的路径。
使用 Redirect (重定向)可以在进入时或刷新后匹配组件都失败时进行重定向。
<Route path="/A" component={A} />
<Route path="/B" component={B} />
<Redirect to='/B' />
重定向一般放在末尾,重定向支持模糊匹配
嵌套路由
嵌套路由即路由组件里面再使用路由组件。因此使用到的路由不止一层。可能有多层。
基本使用
如 : 页面主要有两个路由组件 , A 和 B 。 而 B组件下又有 C 和 D 两个路由组件。
A, B 路由组件的使用以上有。
B 内增加第二级路由与仅一级路由使用方法类似
-
为方便管理,在 B 组件的文件夹下创建 C ,D 组件 -
在B组件内 设置 C 和 D 组件的链接 和 对应组件 ,(要引入相关标签) 因为是 B 下的, 因此 to 和 path 属性必须以 /B 开头 ,格式如: /B/xxx .
import React, { Component } from 'react'
import C from './C/C'
import D from './D/D'
import { NavLink, Route, Switch } from 'react-router-dom'
export default class B extends Component {
render() {
return (
<div>
B 组件
<hr/>
<NavLink to='/B/C' >C 组件</NavLink>
<NavLink to='/B/D'>D 组件</NavLink>
<Switch>
<Route path='/B/C' component={C}></Route>
<Route path='/B/D' component={D}></Route>
</Switch>
</div>
)
}
}
此时的 Redirect 可以重定位到二级路由
<Route path="/A" component={A} />
<Route path="/B" component={B} />
<Redirect to='/B/D' />
多级定位的方法也相同 , 如 C 下的 E ,匹配路径就得 是 /B/C/E
路由组件传参
1. params
利用 params 传参 ,即在跳转路由后加上需要传递的参数 ,适合传递较简单,量少的数据。
步骤 :
-
在跳转的链接标签的 to 属性值后添加数据,格式是 to='.../value1/value2/...' -
在路由组件标签 Route 的 path 属性里声明接收的参数名 ,如:path='.../:key1/:key2/...' -
使用数据 。传递的参数会传递到 组件实例的 props 属性的 match 里 。 访问方法是 : this.props.match.params.key
demo:
// 传递两个数据 ,一个是 001 , 一个是 js
<NavLink to='/A/001/js'>A 组件</NavLink>
// 声明接收两个数据 , 第一个数据叫 id , 接收到 001 ,第二个叫 name , 接收到 js
<Route path="/A/:id/:name" component={A} />
// A 组件输出
console.log(this.props.match.params);
// {id: '001', name: 'js'}
2. search
search 传递参数的形式与 query 的形式一样 。 通过 ? 分隔 URL 和数据 。通过 & 分隔数据。
- 通过 query 形式书写链接标签的 to 属性值,格式 :
url ? key1=value1/ key2= value2 - 对于 Route 标签不需要其他修改 ,与 params 不同, prarams 若不声明参数,则参数会被当做是路径的一部分。
- 接收 , 通过
this.props.location.search ,返回一个字符串,包含参数。
demo:
// 传递两个数据 ,一个是 001 , 一个是 js
<NavLink to='/A/?id=001/name=js'>A 组件</NavLink>
// Route 不需要修改路径
<Route path="/A" component={A} />
// A 组件输出
console.log(this.props.location.search);
// ?id=001/name=js
使用这个方法无论是传参还是使用参数都比较麻烦。因为返回的是一个字符串。
但 React 脚手架 自动给项目安装了一个库 ,叫 querystring , 这个库能将对象转换成 query 形式的参数字符串 ,也能将参数字符串转化成对象。
使用这个库需要先导入
import qs from 'querystring'
导入的 qs 对象有两个方法 , 一个是 stringfy 方法 ,将对象转换成字符串(无问号)
qs.stringfy({
id:123,
name:'xm'
})
另一个是 parse 方法 ,将参数字符串转化成对象(字符串需先去掉问号)
qs.parse('id=123&name=xm') // 注意 ,无 ? 号
// 返回 {id: '123', name: 'xm'}
利用这两个方法对于路由的传参和组件接收就便利了很多
demo
// 在 链接标签的页面和组件页面都导入 querystring 库
import qs from 'querystring'
let query = {
id:'123',
name:'xm',
....
}
// 记得加上 ? 号
<NavLink to={`/A/?${qs.stringfy(query)}`}>A 组件</NavLink>
// A 组件输出
console.log(qs.parse(this.props.location.search.slice(1))); // slice 去除问号
// {id: '123', name: 'xm',...}
3.state传参
这个 state 与组件的 state 无关系 。 这个 state 属于路由的 。
使用 :
-
链接标签的 to 属性值 不再是一个字符串 而是一个 对象 。对象里有两个属性 ,一个是 pathname , 表示路由 , 另一个是 state ,值是一个对象,对象里包含需要传递的参数数据。 如 :to={{pathname:'/A' , state:{id:123}}} -
Route 标签无需更改 -
在组件 通过 this.props.location.state 得到 传入的 state 对象。里面包含参数数据。
demo:
<NavLink to={{ pathname: '/A', state: { id: 123 } }} >A 组件</NavLink>
// 组件内
console.log(this.props.location.state);
// {id: 123}
特点:
- 不会在页面的URL栏显示参数
- to 属性值是一个对象。
- 虽然路径不显示具体参数了 ,但与前几个一样,刷新后不会更改URL状态和清空参数。
push 和 repalce
react 路由切换默认是 push 的 。也就是有记录的。可以通过 history.back() ,history.go() 等方法回到之前的路由,再然后也可以前进一个路由。或点击游览器 URL 左侧的前进回退箭头。
而 react 的链接标签有一个 replace 属性 , 值是布尔值 ,但一般 false 不需要写。因为 false 就是默认 push 。
所以为· true 时,直接写上 replace 即可。
<NavLink replace ..../>
开启后点击这个链接 ,URL 会进行 replace 跳转 。 之前的记录会被清空。以就是无法回到之前的 URL 。
编程式路由跳转
对于使用 NavLink 和 Link 链接标签实现跳转确实方便,但有缺点,就是跳转过程不再受我们控制。如希望点击后进行相关操作后再跳转。
而标签也是固定的。如果我们希望点击一个图片,一个视频再跳转。那么会比较麻烦。
对此 。我们可以使用编程式路由跳转。
编程式跳转可以由我们决定什么时候跳转 ,并非点击时,也可以是触发其他事件,或者处于某种状态。
编程式路由跳转的实现基础是使用路由组件里的 props 里的 history 属性。
这也意味着只有路由组件可以实现编程式路由跳转。
this.props.history
组件路由的 props 上有一个 histroy 属性。这个属性保存的值是一个对象。这个对象有2个方法可以实现跳转。
分别是 push 和 repalce 区别在上面有。
这两个方法都有两个参数 。
使用编程式路由跳转的 Route 标签如何书写根据传参方法决定
也就是使用 params 传参时需要声明参数名,其他不需要修改上面
这里我们是点击跳转,简单的展示如何书写。
实现过程:
- 给父路由组件标签添加一个点击事件,给事件处理函数传递参数,参数最后是传给子路由组件的
- 给父路由组件的实例上增加一个方法,用来跳转。调用 this.props.history 上的 push 或 repalce 方法
demo:
// 父路由组件
<button onClick={() => this.to({ way: 'push', id: 12, name: 'xh' })}>push</button>
<button onClick={() => this.to({ way: 'replace', id: 12, name: 'xh' })}>replace</button>
<button onClick={() => this.to({ way: 'state', id: 12, name: 'xh' })}>state</button>
switch (way) {
case 'push': {
// push - params
this.props.history.push(`/B/C/${id}/${name}`)
return
};
case 'replace': {
// replce - search
this.props.history.replce(`/B/C/?${id}&${name}`)
return
};
case 'state': {
// state
this.props.history.push('B/C', { id, name })
return
}
}
}
withRouter
withRouter 方法是 react-router-dom 暴露的一个方法。可以实现在非路由组件上进行编程式跳转。
在前面编程式路由跳转有强调到 只有路由组件的 props 里才有 history 属性。
使用只有路由组件才能编程序跳转。但是我们的需求可能就是要在非路由组件上进行编程式跳转。
为了能实现在非路由组件上进行编程式跳转 , react的路由库给我们提供了一个 withRouter。
在非路由组件上进行编程式跳转的操作与在路由组件上的操作是完全相同的。只有一点不同。
就是导出使用了编程式的非路由组件时,我们先将组件当做参数传给 withRouter 函数。
再将返回值导出。
import React, { Component } from 'react'
// 导入 withRouter
import { withRouter } from 'react-router-dom'
class A extends Component{
// .....
// 使用了 编程式跳转
}
// 使用 withRouter
export default withRouter(A)
// 使用 组件 A
import A from './pages/A/A'
export default class App extends Component {
render(){
return (
<div>
<A/>
</div>
)
}
}
如代码所示,可知
- withRouter 返回的还是一个组件
- withRouter 可以加工一般组件,让其具有路由组件才能使用的 API 。
BrowserRouter 和 HashRouter 的区别
- 底层原理不一样
- BrowserRouter 使用的是 H5 的 history API , 不兼容 IE9 以下的版本
- HashRouter使用的是 URL 的哈希值
- path 的表现形式不一样
- BrowsRouter 的表现形式是 :…/demo/test
- HashRouter 的表现形式是 : …/#/demo/test
- 刷新后对 state 参数的影响不一样
- BrowserRouter 刷新后对 state 参数没有影响
- HashRouter 刷新后 state 参数会丢失
参考来源: B站尚硅谷React教程 菜鸟教程 B站codewhy老师的 vuerouter 教学部分
|