整个应用只有一个完整的页面,称为单页Web应用。
点击页面链接时不会刷新页面,只会做页面的局部刷新。
数据通过ajax请求获取,在前端异步展现。
一个路由就是一个映射关系(key: value),key为路径,value为function或component。
后端路由
router.get(path, function(req, res)前端路由
前端路由的原理主要是靠BOM的history。
https://react-router.docschina.org/web/guides/philosophy
安装库:npm i react-router-dom
import { Link, BrowserRouter, Route } from 'react-router-dom'
<div>
{/* 使用路由链接切换组件 */}
<BrowserRouter>
<Link className="list-group-item" to="/about">AboutLink>
<Link className="list-group-item" to="/home">HomeLink>
BrowserRouter>
div>
<div>
{/* 注册路由--编写路由链接 */}
<BrowserRouter>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
BrowserRouter>
div>
问题:此时点击切换组件时,只有链接变化,组件不会变化。
原因:两者分别用BrowserRouter包裹,意味着它们位于两个路由器当中,是相互独立的。
解决:使用一个路由器包裹全部内容
的组件,所以可以直接在index.js文件中用路由器将包裹起来。// 引入路由器
import { BrowserRouter } from 'react-router-dom'
// 渲染 App 到页面
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
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可以用于解决一些路径错误相关的问题。
一般组件:
路由组件:


选中标签时为其添加高亮效果。
设计原理:动态添加样式类名active。
使用Link:

使用NavLink:

可以在NavLink中传递属性activeClassName来实现点击时加哪一个样式的类名。
{/* 相当于不写activeClassName,因为原本加的类名就是active */}
<NavLink activeClassName='active' className="list-group-item" to="/about">AboutNavLink>
<NavLink activeClassName='addactive' className="list-group-item" to="/about">AboutNavLink>
<NavLink activeClassName='addactive' className="list-group-item" to="/home">HomeNavLink>
.addactive {
background-color: bisque !important;
}

二次封装后可以减少代码内的重复内容。
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
// 获取传递过来的props值
const { to, title } = this.props
return (
<NavLink activeClassName='addactive' className="list-group-item" to={to}>{title}</NavLink>
);
}
}
<div className="list-group">
{/* 使用路由链接切换组件 */}
{/* <NavLink activeClassName='addactive' className="list-group-item" to="/about">AboutNavLink>
<NavLink activeClassName='addactive' className="list-group-item" to="/home">HomeNavLink> */}
<MyNavLink to="/about" title="About" />
<MyNavLink to="/home" title="Home" />
div>
标签体内容其实也包含在props中,是标签的一个特殊属性:
<MyNavLink to="/about">AboutMyNavLink>
<MyNavLink to="/home">HomeMyNavLink>
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
// 获取传递过来的props值
return (
<NavLink activeClassName='addactive' className="list-group-item" {...this.props} />
);
}
}
当页面中注册的路由非常多时,如果已经匹配到了目标路径,就应该停止往下查找,避免产生过多的消耗性能。
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />

此时可以使用Switch标签将所有的路由包裹起来,实现单一匹配,从而提高效率。
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
Switch>

/home/*

2. 严格匹配:使用exact属性可以开启精准匹配


注意:
严格匹配不要随意开启,否则有时会导致无法继续匹配二级路由
重定向放在路由注册最下方,如果前面的路由都没有匹配上,则由重定向决定路由指向。
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
Switch>
在注册子路由时要写上父路由的path值,路由的匹配是按照注册路由的顺序进行的。
在Home组件中添加子组件:
<div>
<h2>我是Home的内容h2>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">NewsMyNavLink>
li>
<li>
<MyNavLink to="/home/message">MessageMyNavLink>
li>
ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
Switch>
div>
div>
{/* 注册路由--编写路由链接 */}
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/home/news" />
Switch>

父组件向路由组件传递params参数:

新建Detail子组件,并在组件中接收params参数:
import React, { Component } from 'react';
const detailData = [
{ id: '01', content: 'aaa' },
{ id: '02', content: 'bbb' },
{ id: '03', content: 'ccc' }
]
export default class Detail extends Component {
render() {
const { id, title } = this.props.match.params
const findResult = detailData.find((detailObj) => {
return detailObj.id == id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
);
}
}
向路由组件传递params参数后,子组件中能通过this.props获取到传递的值

向路由组件传递search参数

在子组件中获取到的search参数为以下效果,需要对其进行处理才能使用

引入qs库,能将urlencoded(即key=value&key=value格式)转为对象形式
import React, { Component } from 'react';
import qs from 'qs'
const detailData = [
{ id: '01', content: 'aaa' },
{ id: '02', content: 'bbb' },
{ id: '03', content: 'ccc' }
]
export default class Detail extends Component {
render() {
// 接收search参数
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
const findResult = detailData.find((detailObj) => {
return detailObj.id == id
})
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
);
}
}
向路由组件传递state参数

子组件中接收值:
import React, { Component } from 'react';
const detailData = [
{ id: '01', content: 'aaa' },
{ id: '02', content: 'bbb' },
{ id: '03', content: 'ccc' }
]
export default class Detail extends Component {
render() {
// 接收state参数
const { id, title } = this.props.location.state || {}
const findResult = detailData.find((detailObj) => {
return detailObj.id == id
}) || {}
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
);
}
}
当页面刷新的时候也可以保留参数
用法:直接在路由标签中写replace属性
可以借助路由身上的history对象上的API来实现路由跳转:
例子:
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 })
}
render() {
const { messageArr } = this.state;
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
{/* 向路由组件传递params参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递search参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递search参数 */}
<Link to={{ pathname: "/home/message/detail", state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
<button onClick={() => this.pushShow(msgObj.id, msgObj.title)}>push查看</button>
<button onClick={() => this.replaceShow(msgObj.id, msgObj.title)}>replace查看</button>
</li>
)
})
}
</ul>
<hr />
{/* 接收params参数 */}
{/* */}
{/* 接收search / state参数 */}
<Route path="/home/message/detail" component={Detail}></Route>
</div>
);
}
}
const detailData = [
{ id: '01', content: 'aaa' },
{ id: '02', content: 'bbb' },
{ id: '03', content: 'ccc' }
]
export default class Detail extends Component {
render() {
// 接收params参数
// const {id, title} = this.props.match.params
// 接收search参数
// const { search } = this.props.location
// const { id, title } = qs.parse(search.slice(1))
// 接收state参数
const { id, title } = this.props.location.state || {}
const findResult = detailData.find((detailObj) => {
return detailObj.id == id
}) || {}
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
);
}
}
问题:一般组件当中直接调用this.props.history结果为空,无法使用路由组件中的API
解决:使用withRouter加工一般组件,让一般组件具备路由组件所特有的API,其返回值是一个新组件
import React, { Component } from 'react';
// 引入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>
);
}
}
// withRouter能接收一般组件,并在一般组件身上加上路由组件特有的属性
export default withRouter(Header)