React 路由

相关理解

SPA的理解

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

路由的理解

什么是路由?

  1. 一个路由就是一个映射关系(key:value)
  2. key为路径, value可能是functioncomponent

路由分类

后端路由
  1. 理解: value是function, 用来处理客户端提交的请求。
  2. 注册路由: router.get(path, function(req, res))
  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由
  1. 浏览器端路由,value是component,用于展示页面内容。
  2. 注册路由: <Route path="/test" component={Test}>
  3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

react-router-dom的理解

1
yarn add react-router-dom

相关概念

  1. react的一个插件库。
  2. 专门用来实现一个SPA应用。
  3. 基于react的项目基本都会用到此库。

相关api

1、内置组件
  1. <BrowserRouter>
  2. <HashRouter>
  3. <Route>
  4. <Redirect>
  5. <Link>
  6. <NavLink>
  7. <Switch>
2、其他
  1. history对象
  2. match对象
  3. withRouter函数

路由的基本使用

1.明确好界面中的导航区、展示区

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

1
<Link to="/xxxxx">Demo</Link>

3.展示区写Route标签进行路径的匹配

1
<Route path='/xxxx' component={Demo}/>

4.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>

1
2
3
4
5
6
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)

路由组件与一般组件

  1. 写法不同:

一般组件:<Demo/>

路由组件:<Route path="/demo" component={Demo}/>

  1. 存放位置不同:

一般组件:components

路由组件:pages

  1. 接收到的props不同:

​ 一般组件:写组件标签时传递了什么,就能收到什么

​ 路由组件:接收到三个固定的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//路由属性打印结果展示
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"
  1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {NavLink,Route} from 'react-router-dom'
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}

{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>react脚手架</title>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="stylesheet" href="/css/bootstrap.css">
<style>
.atguigu{
background-color: rgb(209, 137, 4) !important;
color: white !important;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
  1. 封装
1
2
3
4
5
6
7
8
//封装示例
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
  1. 使用与调用
1
2
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>

注:标签体内容也是一种特殊的标签属性

1
2
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/about" children="About"/>

Switch的使用

1.通常情况下,path和component是一一对应的关系。

2.Switch可以提高路由匹配效率(单一匹配) —- 即匹配到一个后将不再往下匹配

1
2
3
4
5
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>

解决多级路径刷新页面样式丢失的问题

1.public/index.html 中 引入样式时不写 .// (常用)./相对路径

2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用,但只在react中有效果)绝对路径

3.使用HashRouter (不常用) #后面自动忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>react脚手架</title>
<!-- 方法二 -->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<!-- 方法一 -->
<link rel="stylesheet" href="/css/bootstrap.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

补充:yarn startnpm start都可,但安装包时尽量用一种

路由的严格匹配与模糊匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
  2. 开启严格匹配:<Route exact={true}path="/about" component={About}/>

​ 可以省略exact={true}exact

  1. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
1
2
3
4
5
6
7
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>

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

Redirect的使用

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
1
import {Route,Switch,Redirect} from 'react-router-dom'
  1. 具体编码:
1
2
3
4
5
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>

嵌套路由

  1. 注册子路由时要写上父路由的path值
  2. 路由的匹配是按照注册路由的顺序进行的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-------------------注册一级路由-----------------------------
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
----------------------注册二级路由 :Home组件-----------------------------------
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
</div>

向路由组件传递参数

params参数

  1. 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
  2. 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
  3. 接收参数:this.props.match.params子组件
1
2
3
4
5
6
7
8
9
10
-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<hr />
{/* 声明接收params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
const {id,title} = this.props.match.params

优势: 刷新地址栏,参数依然存在

缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。

search参数

  1. 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  3. 接收参数:this.props.location.search
  4. 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
1
2
3
4
5
6
7
8
9
10
11
12
13
-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
<hr />
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
import qs from 'querystring'
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))

优势: 刷新地址栏,参数依然存在

缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。

state参数

  1. 路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  3. 接收参数:this.props.location.state
  • 备注:使用BrowserRouter刷新才可以保留住参数,使用HashRouter刷新后state将会没有history来保存参数
  • 子组件接受参数时const {id,title} = this.props.location.state || {} ,后面添加||{}是防止state为undefined时报错
1
2
3
4
5
6
7
8
9
10
11
-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title} </Link>
<hr />
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
// 接收state参数,后面添加`||{}`是防止state为undefined时报错
const {id,title} = this.props.location.state || {}

优势:传参优雅,传递参数可传对象;

缺点:刷新地址栏, HashRouter可能会丢失参数

编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退

  1. this.prosp.history.push()

​ 将历史记录压入栈

  1. this.props.history.replace()

​ 替代栈位置,即不会产生历史记录

  1. this.props.history.goBack()

​ 回退一格

  1. this.props.history.goForward()

​ 前进一格

  1. this.props.history.go()

​ 前进或者后退n格(根据传入的数字正负数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail'

export default class Message extends Component {
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' },
]
}

replaceShow = (id, title) => {
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)

//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)

//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`, { id, title })
}

pushShow = (id, title) => {
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)

//push跳转+携带search参数
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)

//push跳转+携带state参数
this.props.history.push(`/home/message/detail`, { id, title })

}

back = () => {
this.props.history.goBack()
}

forward = () => {
this.props.history.goForward()
}

go = () => {
this.props.history.go(-2)
}

render() {
const { messageArr } = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>

{/* 向路由组件传递params参数 */}
{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

{/* 向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

{/* 向路由组件传递state参数 */}
<Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
&nbsp;<button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>
&nbsp;<button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>
</li>
)
})
}
</ul>
<hr />
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}

{/* search参数无需声明接收,正常注册路由即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}

{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail} />

<button onClick={this.back}>回退</button>&nbsp;
<button onClick={this.forward}>前进</button>&nbsp;
<button onClick={this.go}>go</button>
</div>
)
}
}

withRouter的使用

  1. withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
  2. withRouter的返回值是一个新组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
back = () => { this.props.history.goBack()}
forward = () => {this.props.history.goForward()}
go = () => { this.props.history.go(-2)}
render() {
console.log('Header组件收到的props是', this.props);
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>&nbsp;
<button onClick={this.forward}>前进</button>&nbsp;
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)

BrowserRouter与HashRouter的区别

底层原理不一样

  1. BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。但一般来说都用的这个
  2. HashRouter使用的是URL的哈希值

path表现形式不一样

  1. BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
  2. HashRouter的路径包含#,例如:localhost:3000/#/demo/test

刷新后对路由state参数的影响

  1. BrowserRouter没有任何影响,因为state保存在history对象中。
  2. HashRouter刷新后会导致路由state参数的丢失!!!(不一定)

备注:HashRouter可以用于解决一些路径错误相关的问题。

每个 React Router 应用程序的核心都应该是一个路由组件。对于 web 项目,react-router-dom提供<BrowserRouter><HashRouter>路由器。两者之间的主要区别在于它们存储 URL 以及与您的 Web 服务器通信的方式。

  • <BrowserRouter>使用常规 URL 路径。这些通常是最好看的 URL,但它们需要正确配置您的服务器。具体来说,你的 Web 服务器需要在所有由 React Router 管理的客户端提供相同的页面。Create React App 在开发中开箱即用地支持这一点,并附带有关如何配置生产服务器的说明。
  • <HashRouter>将当前位置存储在URL的hash一部分中,因此 URL 看起来像http://example.com/#/your/page. 由于哈希永远不会发送到服务器,这意味着不需要特殊的服务器配置

Hook

useHistory

useHistory挂钩使您可以访问history可用于导航的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useHistory } from "react-router-dom";

function HomeButton() {
let history = useHistory();

function handleClick() {
history.push("/home");
}

return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}

useLocation

useLocation钩子返回location表示当前 URL 的对象。您可以将其视为每当 URL 更改时useState返回一个新的。location

这可能非常有用,例如,当您希望在新页面加载时使用 Web 分析工具触发新的“页面查看”事件时,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Switch,
useLocation
} from "react-router-dom";

function usePageViews() {
let location = useLocation();
React.useEffect(() => {
ga.send(["pageview", location.pathname]);
}, [location]);
}

function App() {
usePageViews();
return <Switch>...</Switch>;
}

ReactDOM.render(
<Router>
<App />
</Router>,
node
);

useParams

useParams返回 URL 参数的键/值对的对象。用它来访问match.params当前的<Route>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Switch,
Route,
useParams
} from "react-router-dom";

function BlogPost() {
let { slug } = useParams();
return <div>Now showing post {slug}</div>;
}

ReactDOM.render(
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route path="/blog/:slug">
<BlogPost />
</Route>
</Switch>
</Router>,
node
);