 React - 路由
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}/>1
- App.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
 6
- Switch 可以提高路由匹配效率(单一匹配),只要找到第一个匹配的 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.params1
# 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-loader1
- 修改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' 应该删掉。
