React - 路由
# 路由的概念
# 什么是路由?
一个路由就是一个映射关系(key:value)
key 为路径,value 可能是 function 或 component
# 路由分类
后端路由:
理解: value 是 function,用来处理客户端提交的请求
注册路由:
router.get(path, function(req, res))
工作过程:当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
前端路由:
浏览器端路由,value 是 component,用于展示页面内容。
注册路由:
<Route path="/test" component={Test}>
工作过程:当浏览器的 path 变为
/test
时,当前路由组件就会变为 Test 组件
# 路由基石
history 模式(操作 H5 的 BOM 的 history 对象)
hash 模式(原有的 url 后面加入
/#/
)
# 路由原理
push:在原来的 uri 后面追加新的地址,存入历史记录,可以返回上一个页面
replace:替换原来的 uri,不存入历史记录,无法返回上一个页面
goBack:返回上一个历史记录页面
forward:前往下一个历史记录页面
# 路由种类
react-router-dom:前端开发路由(我们用)
react-router-native:基于 React 原生开发路由
react-router-any:任何环境开发路由
# 路由的基本使用
明确好界面中的导航区、展示区
导航区的 a 标签改为 Link 标签
<Link to="/xxxxx">Demo</Link>
1展示区写 Route 标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
1App.jsx
文件的<App>
的最外侧包裹了一个<BrowserRouter>
或<HashRouter>
import About from './components/About' render(){ return ( <div> <div> <Link className="list-group-item" to="/about">About</Link> </div> <div> <Route path="/about" component={About}/> </div> </div> ) }
1
2
3
4
5
6
7
8
9
10
11
12
13
# 路由组件与一般组件区别
写法不同:
一般组件:
<Demo/>
路由组件:
<Route path="/demo" component={Demo}/>
存放文件夹位置不同:
一般组件:components
路由组件:pages
接收到的 props 不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性(history,location,match),如下:
// 建议往下面看到编程式路由导航,再回来看更清楚和理解
history:
go: ƒ go(n) // 跳转 n 个页面,n 为正负数值
goBack: ƒ goBack() // 后退一个页面
goForward: ƒ goForward() // 前进一个页面
push: ƒ push(path, state) // push 模式,并且可以携带 state 参数
replace: ƒ replace(path, state) // replace 模式,并且可以携带 state 参数
location:
pathname: "/about"
search: "" // 存有 search 参数,是一个字符串形式
state: undefined // 存有 state 参数
match: // 存有params参数
params: {}
path: "/about"
url: "/about"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Link与NavLink
如果给 Link 加入点击高亮,需要换更高级的标签
NavLink 可以实现路由链接的高亮,通过
activeClassName
指定样式名,不加默认点击时自动追加 active通过
this.props.children
可以获取标签体内容
# 自定义MyNavLink
原因:NavLink 出现多个时,重新写属性不优雅,自己封装一个 MyNavLink,把重复的样式如 className 放入。
组件可以传入属性值,如
<MyNavLink to="/about" a={1} b={2}></MyNavLink>
组件也可以传入标签体内容,如
<MyNavLink>标签体内容</MyNavLink>
- 标签体内容是特殊的属性值,所以标签属性值 也可以写标签体内容,如
<MyNavLink children="标签体内容"></MyNavLink>
- 标签体内容是特殊的属性值,所以标签属性值 也可以写标签体内容,如
传入的属性值存放在 props 里,传入的标签体存放在 props 的 children 里
自定义
MyNavLink
:({...this.props}
),会把 props 里的属性解析出来展示render() { // console.log(this.props); //有 to 属性,也有 children 对象的 About、Home 属性 return ( // <NavLink activeClassName="kele" className="list-group-item" {...this.props}>{this.props.children}</NavLink> //...this.props 有 to 属性,也有 children 对象的 About、Home 属性 <NavLink activeClassName="kele" className="list-group-item" {...this.props}/> ) }
1
2
3
4
5
6
7
8引入:
<MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink>
1
2
# Switch的使用
通常情况下,path 和 component 是一一对应的关系。
如果 path 出现多个一样的路径,则都会匹配上,但是中间出现多个会影响效率
<Switch> <Route path="/about" component={About}/> <Route path="/demo" component={Demoe}/> <Route path="/kele" component={Kele}/> <Route path="/About" component={Test}/> </Switch>
1
2
3
4
5
6Switch 可以提高路由匹配效率(单一匹配),只要找到第一个匹配的 path,则不会往下找。
<Switch> <Route path="/about" component={About}/> <Route path="/demo" component={Demoe}/> <Route path="/kele" component={Kele}/> <Route path="/About" component={Test}/> </Switch>
1
2
3
4
5
6
# 解决多级路径刷新页面样式丢失的问题
原因:当引入的 Css 或者其他文件有 ./
开头,则默认去往前路由路径下找文件,其实文件都是存在于根目录下的 public 文件
解决:
public/index.html 中引入样式时不写
./
,写/
(常用),因为/
代表当前根目录public/index.html 中引入样式时不写
./
,写%PUBLIC_URL%
(常用)使用 HashRouter
# 路由的严格匹配与模糊匹配
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
{/* 可以匹配 */} <BrowserRouter> <MyNavLink to="/home/a/b">Home</MyNavLink> <Route path="/home" component={Home}/> </BrowserRouter> {/* 不可以匹配 */} <BrowserRouter> <MyNavLink to="/home">Home</MyNavLink> <Route path="/home/a/b" component={Home}/> </BrowserRouter>
1
2
3
4
5
6
7
8
9
10使用
exact
属性开启严格匹配(exact 或者 exact={true}):<BrowserRouter> <MyNavLink to="/home/a/b">Home</MyNavLink> <Route exact path="/home" component={Home}/> </BrowserRouter>
1
2
3
4严格匹配不要随便开启,默认不要开,只有需要再开,有些时候开启会导致无法继续匹配二级路由
# Redirect的使用(指定初始路由)
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由,可用于初次进入首页,打开默认组件
具体编码(进入首页,显示 About 组件的内容):
<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>
1
2
3
4
5
# 嵌套路由(多级路由)
注册子路由时要写上父路由的 path 值
原因:不写父路由,默认只找子路由的 path,但是第一批注册的路由没有该 path,只有父路由的 path
路由的匹配是按照注册路由的顺序进行的
{ /* 父路由组件:*/ } <BrowserRouter> <MyNavLink to="/home">Home</MyNavLink> <Route path="/home" component={Home}/> </BrowserRouter> { /* 子路由组件:*/ } <BrowserRouter> <MyNavLink to="/home/message">Message</MyNavLink> <Route path="/home/news" component={News}/> </BrowserRouter>
1
2
3
4
5
6
7
8
9
10
# 向路由组件传递参数
# params参数
路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
1注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
1接收参数:
const {name,age} = this.props.match.params
1
# search参数(可以认为是伪query参数)
路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情</Link>
1注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
1接收参数(字符串形式:
?key=value$......
,没有解析为{key:value,......}
):import qs from 'querystring' ...... const search = this.props.location.search //?name=tom&age=18 字符串 const {id,title} = qs.parse(search.slice(1)); //把?去掉
1
2
3
4备注:获取到的 search 是
urlencoded
编码字符串,需要借助第三方库querystring
或者自己解析!
# state参数
路由链接(携带参数,参数必须为对象,对象包含 子路由路径和要传的参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
1注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
1接收参数:
const {name,age} = this.props.location.state || {}
1备注:url 地址没有参数,类似于 post 提交后的 url 地址,而且刷新也可以保留住参数!
原理:BrowserRouter 有 history 历史记录对象,该对象存有参数的记录,如果清除历史记录缓存,那么刷新会丢失参数!
所以 HashRouter 会刷新丢失参数,具体看 BrowerRouter与 HashRouter 的区别
# 对比
params 参数用的最多,其次是 search 参数,最后是 state 参数,如果不想地址栏有参数显示,就使用 state 参数。
# 编程式路由导航
概念:不需要借助 Link 或者 NavLink 标签进行路由跳转、传参
方法:利用点击事件,借助
this.props.history
对象上的 API 对操作路由跳转、前进、后退,加入// 往后看'路由组件和一般组件区别',里面有更详细的介绍 this.props.history.push(uri,state) // uri 后追加 path,产生历史记录,可携带 state 参数 this.props.history.replace(uri,state) // 替换原来的 path,不产生历史记录,可携带 state 参数 this.props.history.goBack() // 后退 this.props.history.goForward() // 前进 this.props.history.go(n) // 跳转 n 个页面,n 为正负数值 // 例子 export default class Message extends Component{ state = { messageArr:[ {id:'01',title:'消息1'}, {id:'02',title:'消息2'}, {id:'03',title:'消息3'}, ] } replaceShow = (id,title)=>{ //replace 跳转 + 携带 state 参数 this.props.history.replace(`/home/message/detail`,{id,title}) } pushShow = (id,title)=>{ //push 跳转 + 携带 state 参数 this.props.history.push(`/home/message/detail`,{id,title}) } render() { return ( <button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button> <button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button> ) } }
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
# withRouter的使用
withRouter 可以加工一般组件,让一般组件具备路由组件所特有的 API
withRouter 的返回值是一个新组件
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() {
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# BrowserRouter与HashRouter的区别
底层原理不一样:
BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本
HashRouter 使用的是 URL 的哈希值
path 表现形式不一样
BrowserRouter 的路径中没有
#
,例如:localhost:3000/demo/test
HashRouter 的路径包含
#
,例如:localhost:3000/#/demo/test
刷新后对路由 state 参数的影响
BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
HashRouter 刷新后会导致路由 state 参数的丢失
备注:HashRouter 可以用于解决一些路径错误相关的问题
# antd的按需引入+自定主题
安装依赖:
yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
1修改package.json
.... "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" }, ....
1
2
3
4
5
6
7
8根目录下创建 config-overrides.js
//配置具体的修改规则 const { override, fixBabelImports,addLessLoader} = require('customize-cra'); module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: true, }), addLessLoader({ lessOptions:{ javascriptEnabled: true, modifyVars: { '@primary-color': 'green' }, } }), );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'
应该删掉。