30-React杂记(路由)
路由
声明:本次使用的路由包版本是5.3.4【当前最新6.0】
1、介绍
React Router官网:https://reactrouter.com/
使用用React Router前需要先进行安装:
1 | npm i react-router-dom@5 |
React Router现在的主版本是5,思想:一切皆组件。
2、路由的使用
2.1、相关组件
如前面介绍里说的,自Router 4之后的思想是一切皆组件
,所以在正式开始学习React路由前需要先对几个组件要有所掌握:
- Router组件(别名,真实是不存在的,为了简写路由模式的组件名称):包裹整个应用(单个具体的组件/根组件),一个React应用只需要使用一次
- 注意:在react中,不存在类似于vue的路由配置文件,对于前端路由模式的选择,我们可以通过该组件完成
- Router类型: HashRouter和BrowserRouter
- HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
- BrowserRouter:使用H5的history API实现(localhost:3000/first)
- 区别:
- 两者在开发阶段,除了地址栏上的表现形式不一样以外,其它没区别
- 两者在生产阶段,hash路由可以直接上生产,无需做任何配置,而history模式则上生产需要配置的,配置服务器环境 (后台人员服务器配置一下如果404,指定index.html或首页),否则项目是不能刷新的,一刷新就会404
- Link组件:用于指定导航链接(a标签)就是做声明式导航的(类似于vue中的
router-link组件
)- 最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
- Route组件:既可以编写路由规则同时也是路由规则对应的组件的展示容器【路由规则】{path: xx,component:xxx}
- path属性:路由规则,这里需要跟Link组件里面to属性的值一致
- component属性:展示的组件
- 语法:
- 该组件除了具备定义路由规则功能外,还有类似于vue中
router-view
的功能
各个组件之间的关系
注意:
Link
和Route
组件必须被Router
组件给包裹,否则报错。
2.2、声明式导航
- 在
src/index.js
主入口文件中定义一个路由模式(可选,也可以在具体的某个组件中使用Router)
1 | import React from "react"; |
- 在 src/router/index.jsx中定义路由规则,使用Route 组件,一般语法:
1 | // 定义路由规则: |
- 在根组件中引入路由规则(除其他特殊的路由规则外(嵌套路由规则),其他普通的路由规则都需要在根组件中使用)
1 | import React, { Component } from 'react'; |
在写上述代码时注意,路由自带组件的顺序嵌套关系,组件<Link></Link>
和组件<Route></Route>
必须被组件<Router></Router>
给包裹着。
需要注意:
刨除样式的影响,
Route
组件在HTML代码中的位置决定了渲染后其在页面中显示的位置。如果Route
放在最后,则其显示的时候也在最后;若其放在渲染内容的最前面,相应的显示也会在最开始。
2.3、编程式导航
react-router-dom中通过history对象中的push/go等方法实现编程式导航功能,这一点与之前的vue路由还是很相似的。
形如:
1 | // 方式1 |
请勿在根组件中写编程式导航,因为根组件默认是没有props对象,因为只有通过路由跳转的组件, 该组件的props 中才有 history, loaction match 这三个属性,否则没有. 解决方法使用 react-router-dom中提供的高阶组件强化函数 withRouter(组件) 被包裹组件就有三个参数了 如下:
1
2
3 import {withRouter } from 'react-router-dom';
export default withRouter(App);
3、路由参数
路由参数:在Route定义渲染组件时给定动态绑定的参数。
React路由传参方式有三种:
- ==动态路由参数(param)==
- 以“/film/detail/:id”形式传递的数据
- 在目标页面路由中传递
- 在落地组件中通过
this.props.match.params
得到 - 一般用于restful规范下的开发
- 查询字符串(query)
- 通过地址栏中的
?key=value&key=value
传递 - 在落地组件中通过
this.props.location.search
得到 - 由于得到的数据是带“?”的,还需要进一步加工处理之后才能使用,因此建议少用或者不用
- 通过地址栏中的
- 隐式传参(state),通过地址栏是观察不到的
- 不合适写在声明式导航中,写在编程式导航中更加合适
- 一般数用于埋点数据
- 简单的讲,埋点是将部分标记隐藏起来,等待用户去触发,因为这个事情不想让用户看到(需要做一些数据的收集,后续做分析),因此会使用隐式传参的方式(大数据分析)
- 在落地组件中通过
this.props.location.state
得到
接收示例:
1 | constructor(props){ |
4、嵌套路由
1 | const routes = [ |
在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。
例如,路由规则如下
1 | /admin/index |
它们路由前缀的admin是相同的,不同的只是后面一部分。
思想:
借助react路由默认是非严格匹配模式的便利(路由规则是:/abc)
例如。上述路由都有
/admin
开头,那么我们可以在路由定义时定义一个组件的路由规则“/admin”。如果这样做,则上述4个路由都会匹配上这个路由规则。匹配的这个组件我们称之为父组件扩展:如果某个路由需要使用严格模式,请在这个路由上加上一个属性:exact
如:
注意: 如果该一级路由有二级路由,那么该一级路由一定不能设置严格模式
注意:如果需要匹配根路由(/),那么需要设置严格模式,否则所有的路由都匹配
注意:嵌套路由规则的地址需要将上一级的路由地址拼上,(如 二级路由path = ‘/一级路由path/二级路由path’
再在父组件中写嵌套的子路由的匹配规则
实现方式
- 先需要定义个组件,用于负责匹配同一前缀的路由,将匹配到的路由指向到具体的模块
1 | // 该组件为根组件,修改入口文件的根组件地址,让页面显示当前的组件页面 |
- 在如上的一级路由对应的组件Admin.jsx中,定义二级路由规则
1 | import React, { Component } from 'react'; |
- 创建父路由中的子路由需要的组件
5、重定向与404路由
vue中实现路由重定向:
const routes = [
{
path:’/‘, redirect:’/home’
},
{ path:’/mine’,
component:Mine,
children:[
{path:’’, component:Mine1}
]
}
]
5.1、重定向路由
React的重定向路由有以下写法:
在重定向的时候需要知道,从哪里来,到哪里去,因此该组件需要使用2个属性:
- from:匹配需要重定向的路由
- to:需要去往的路由
1 | import React, { Component } from 'react'; |
注意: 一定要将重定向路由规则组件写在其他路由规则之后,且重定向路由组件设置严格模式.
5.2、404路由
项目中少不了404页面的配置,在React里面配置404页面需要注意:
需要用到Switch组件,让其去包裹所有路由的
Route
组件(Switch组件保证只渲染其中一个子路由)1
2
3
4
5
6
7import NotFound from "./Components/404";
<Route>
<NotFound></NotFound>
</Route>
// 或
<Route component={NotFound}></Route>
注意:在404路由的位置,不需要给定具体的路由匹配规则,即不给
path
表示匹配*
,即所有的路由都会匹配,因此用404路由一定要加Switch
匹配一个路由。
例如:
1 | import React, { Component } from 'react'; |
6、三种路由渲染方式
v6中采用了新的属性对组件进行渲染,属性名:element
Route路由渲染组件是用于路由规则匹配成功后组件渲染容器,此组件提供了3种组件渲染方式:
component属性(值为变量对象或函数)
// 第一种方式 使用组件的变量名(最常用) <Route path="/home" component={Home}/>
1
2
3
4
5
6
~~~jsx
import Home from '../views/routerview/Home';
// 第二种方式: component的是为函数形式,返回对应的组件名
// 缺点: 会造成Home组件的props属性丢失,无法使用props中的属性和方法,解决办法需要添加一个props形参, 该形参就是props属性对象,可以解构到组件上,这样就实现了props
<Route path="/home" component={(props) => <Home {...props} />} />
render属性(值只支持函数方式)
// 如果不设置形参props和组件的{...props},会造成该组件Home 的props属性丢失,这样比如使用编程式导航跳转的时候,无法实现跳转 this.props.history.push() <Route path="/home" render={(props) => <Home {...props} />} />
1
2
3
4
5
6
7
8
9
10
11
12
- children属性(值可以是**函数或组件**)
- ~~~jsx
//props 属性会丢失,同上
//注意:children属性为函数时,该组件有个特点,无论该path是否与当前的渲染地址匹配,该渲染方式始终执行(即始终渲染该组件,前提没有Switch组件包裹),如果当前路由地址不匹配的话,其props的match属性为null
// 后面可以使用该特性,自定义路由组件,
<Route path="/about" children={(props) => {
if(props.match){
return <About {...props} />
}
}} />//children属性为组件时, props属性会丢失 同上 <Route path="/about" children={<About {...this.props} />} />
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
**注意点**
- 当children的值是一个函数时,无论当前地址和path路径匹不匹配,都将会执行children对应的函数,当children的值为一个组件时,当前地址和path不匹配时,路由组件不渲染
- children函数方式渲染,会在形参中接受到一个props对象,对象中match属性如果当前地址匹配成功返回对象,否则null
## 7、封装自定义导航组件
目前虽然在react中有导航组件Link,但是与vue相比,vue的router-link支持tag属性(自定义渲染成标签),而这里不支持。
1. 如下代码是使用<Link>组件实现的正常路由切换跳转
```jsx
import React, { Component } from 'react';
import { Link, Route } from 'react-router-dom'
class Routertest extends Component {
render() {
return (
<div>
<ul>
<li><Link to='/shouye' tag='p'>首页</Link></li>
<li><Link to='/fenlei'>分类页</Link></li>
</ul>
<Route path='/shouye' component={Shouye}></Route>
<Route path='/fenlei' component={Fenlei}></Route>
</div>
);
}
}
//1.定义首页组件
class Shouye extends Component {
render() {
return (
<div>我是首页内容</div>
)
}
}
//2.定义分类组件
class Fenlei extends Component {
render() {
return (
<div>我是分类页内容</div>
)
}
}
export default Routertest;
```
2. 使用自定义导航组件方式实现渲染
```jsx
// 1. 父组件代码
import React, { Component } from 'react';
import { Link, Route } from 'react-router-dom'
// 引入Nav组件
import Nav from '../components/Nav'
class Routertest extends Component {
render() {
return (
<div>
<ul>
<li><Nav to='/shouye' tag='p'>首页</Nav></li>
<li><Nav to='/fenlei'>分类页</Nav></li>
</ul>
<Route path='/shouye' component={Shouye}></Route>
<Route path='/fenlei' component={Fenlei}></Route>
</div>
);
}
}
//1.定义首页组件
class Shouye extends Component {
render() {
return (
<div>我是首页内容</div>
)
}
}
//2.定义分类组件
class Fenlei extends Component {
render() {
return (
<div>我是分类页内容</div>
)
}
}
export default Routertest;
```
```jsx
// 定义 Nav组件代码
import React, { Component } from 'react';
// 需要导入
import { Route, withRouter } from 'react-router-dom'
class Nav extends Component {
render() {
const text = this.props.children // 获取要显示的文本内容
const Tag = this.props.tag ? this.props.tag : 'a' // 获取要显示的标签
const url = this.props.to // 获取要跳转的路径
const style = { cursor: 'pointer' } // 设置样式
return (
<>
<Route path={url} children={(props) => {
if (props.match) { // 判断当前页面路径与当前的path路径匹配,当前标签设置样式,就 是为了这个判断,要不然可以不用Route,直接使用Tag
style.color = 'red'
}
return <Tag onClick={() => this.go(url)} style={style}>{text}</Tag>
}}></Route>
</>
);
};
go(url) {
//console.log(this, props);
// 注意:此处也可以使用children接收的props形参,在go函数的形参中接收该props,这样也可以使用
// props.history.go(url) 实现
this.props.history.push(url)
}
}
// withRouter:高阶组件的强化函数,该函数可以让没有路由信息的组件获取路由信息和方法
export default withRouter(Nav);
```
知识点:6小节中的三种渲染模式
## 8、withRouter
**作用:把不是通过路由切换过来的组件中,将react-router 的 history、location、match 三个对象传入props对象上**
默认情况下,必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,才能执行this.props.history.push('/uri')跳转到对应路由的页面。然而不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props。
~~~jsx
// 引入withRouter
import { withRouter} from 'react-router-dom'
// 执行一下withRouter
export default withRouter(Cmp)
该高阶组件是路由包自带的东西,因此只需要引入+使用就可以了,不需要自己定义。
V5与V6的变化
Switch被废弃,由Routes替代,并且Routes是必须的
渲染组件的方式被element属性替代,例如:
<Route path="/dashboard" element={<Dashboard />} />
重定向组件Redirect被废弃,使用以下语法替代:
<Route path="/" element={<Navigate to="/dashboard/welcome" />} />
1
2
3
4
5
6
7
- 嵌套路由被简化
- 父路由:
- ~~~js
<Route path="/dashboard/*" element={<Dashboard />} />子路由(子路由规则中不再需要写父前缀):
<Route path="welcome" element={<Welcome/>}/>
<Link to="twoa">twoa</Link> <Link to="twob">twob</Link> <Outlet />1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
父路由
~~~~
<Routes>
<Route path="/one" element={<One />} />
<Route path="/two" element={<Two />}>
<Route index element={<Twoa />} />
<Route path='/two/twoa' element={<Twoa />} />
<Route path='/two/twob' element={<Twob />} />
</Route>
<Route path="/" element={<Navigate to="/one" />} />
</Routes>
~~~~
子路由
import { Link, Navigate, Outlet } from 'react-router-dom'import One from "../views/one/One"; import Two from "../views/two/Two"; import Twoa from "../views/two/Twoa"; import Twob from "../views/two/Twob"; import { Navigate } from "react-router-dom"; export const routes=[ { path:"/one", element:<One /> }, { path:"/two", element:<Two />, children:[ { path:"", element:<Twoa /> }, { path:'twoa', element:<Twoa /> }, { path:"twob", element:<Twob /> } ] }, { path:"/", element:<Navigate to="/one" /> } ]1
2
3
4
5
6
7
## useRoutes
可以写路由表,跟据路由表进行切换
router/index.jsimport {NavLink, useRoutes} from 'react-router-dom' import { routes } from './router'; <NavLink to="/one">one</NavLink> <NavLink to="/two">two</NavLink> {useRoutes(routes)}1
2
3
在组件里App.jsx