单页Web应用(single page web application,SPA)
整个应用只有一个完整的页面
点击页面中的链接不会刷新页面,只会做页面的局部更新
数据都需要通过ajax请求获取,并在前端异步展现
什么是路由
一个路由就是一个映射关系(key:value)
key为路径, value可能是function或component
路由分类
后端路由
前端路由
相关概念
react的一个插件库
专门用来实现一个SPA应用
基于react的项目基本都会用到此库
相关api
内置组件
其他
history对象
match对象
withRouter函数
1、 明确好界面中的导航区、展示区
2、 导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
3、 展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
4、
的最外侧包裹了一个
//路由编写和路由注册都靠一个BrowserRouter去管理,所以BrowserRouter写外侧,一直扩
//还有一种就是BrowserRouter写在入口文件中,包住App就可以
ReactDOM.render(
<BrowserRouter><App/></BrowserRouter>,
document.getElementById('root')
)
细节:Link最终在浏览器转换为a标签
写法不同
一般组件:
路由组件:
存放位置不同
一般组件:components
路由组件:pages
接收到的props不同
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到路由器传递的三个固定属性
//路由属性打印结果展示
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"
Link标签,不能追加一个高亮样式,用Link的升级版:
NavLink可以
实现路由链接的高亮
,通过activeClassName指定样式名
默认高亮就是active,正好与bootstrap样式类名相同。我们也可以自己指定样式(权重给高一点盖过bootstrap)
.NavTitle {
background-color: rgb(183, 238, 119) !important;
color: #fff !important;
}
<NavLink activeClassName='NavTitle' className="list-group-item" to='/about'>About</NavLink>
<NavLink activeClassName='NavTitle' className="list-group-item" to='/home'>Home</NavLink>
要是按上面这样写,感觉重复内容有点多,于是可以封装一下:
封装MyNavLink(一般组件)
props用于传递标签属性,标签体内容怎么接收呢!
重点:标签体内容其实也是特殊的属性,只不过我们只能写value,key是人家指定的children;所以我们其实也可以不写标签体内容,然后通过children标签属性来配置
//封装示例
export default class MyNavLink extends Component {
render() {
return (
/* 注意:这里也把标签体内容带过去了,在children属性里面 */
<NavLink activeClassName="NavTitle" className="list-group-item" {...this.props}/>
)
}
}
//使用--标签体内容直接写,props会帮我们收集到children标签属性里面
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
通常情况下,path和component是一一对应的关系
但是如果出现两个相同的路径,匹配两个不同的组件,我们想让它匹配到第一个之后就结束了(提高效率)
Switch可以提高路由匹配效率(单一匹配) ---- 即匹配到一个后将不再往下匹配
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
为什么会丢失
写多级路由的时候,刷新页面之后会丢失,浏览器会把前面一级的路由当作public文件夹下的,错误路径返回的结果由index.html兜底
三种解决方法
public/index.html 中 引入样式时不写 ./ 写 / (常用)
public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL%
(常用,但只在react中
有效果)
使用HashRouter (不常用),#后面的是前端资源,不带给服务器
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>
给的路径是/home,人家要的是/home/a/b,不匹配
给的路径是/home/a/b,人家要的是/home,匹配—模糊匹配
给的路径是/a/home/b,人家要的是/home,不匹配
模糊匹配
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】多了可以不能少,且顺序要一致)
严格匹配
开启严格匹配:exact={true}
为exact
注意:严格匹配不要随便开启
,需要再开,有些时候开启会导致无法继续匹配二级路由
//编写路由链接
<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指定的路由
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
注册子路由时要写上父路由的path值
路由的匹配是按照注册路由的顺序进行的
-------------------注册一级路由-----------------------------
{/* 在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>
路由链接(携带参数):详情
注册路由(声明接收):
接收的参数存在参数:this.props.match.params
-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递params参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<hr />
{/* 声明接收params参数 */}
<Route path="/home/messages/detail/:id/:title" component={Detail} />
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
const {id,title} = this.props.match.params
路由链接(携带参数):详情
注册路由(无需声明
,正常注册即可):
接收参数:this.props.location.search
(没帮我们整理成对象形式)
备注:获取到的search是urlencoded编码字符串
,需要借助qs库解析
注意:由于search接收的时候没帮我们处理成对象形式,所以我们需要借助react中的库来处理。新版的库与之前的不同import qs from 'qs'
name=tom&age=18这种编码形式叫做urlencoded编码形式
对象转urlencoded:qs.stringify(obj)
urlencoded转对象:qs.parse(str)
-------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
<hr />
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/messages/detail" component={Detail}/>
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
import qs from 'qs'
// 接收search参数
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
地址栏是看不见参数的,但是刷新数据不会丢,因为我们目前使用的是BrowserRouter,一直操作浏览器的history(location是history中的一个属性,不会丢;清空历史记录的话就会丢)
路由链接(携带参数):详情
,to写成对象
注册路由(无需声明,正常注册即可):
接收参数:this.props.location.state
BrowserRouter
刷新才可以保留住参数
,使用HashRouter
刷新后state将会没有history
来保存参数const {id,title} = this.props.location.state || {}
,后面添加||{}
是防止使用HashRouter
后state为undefined时报错 -------------------------------发送参数:父组件----------------------------------------------
<div>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/messages/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
<hr />
{/* state参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
</div>
--------------------------------接受参数:子组件-----------------------------------------------------------
// 接收state参数,后面添加`||{}`是防止使用`HashRouter`后state为undefined时报错
const {id,title} = this.props.location.state || {}
push是压栈操作,会留下历史记录。replace是替换操作,把栈顶元素替换成当前元素。
push模式:默认开启push模式
replace模式:replace={true}
,可以简写为 replace
<Link replace={true} to={{ pathname: '/home/messages/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link>
有一个需求:等3秒钟自动路由跳转,借助Link和NavLink难以实现
借助this.prosp.history对象上的API对
操作路由跳转、前进、后退
this.prosp.history.push()
将历史记录压入栈
this.props.history.replace()
替代栈位置,即不会产生历史记录
this.props.history.goBack()
回退一格
this.props.history.goForward()
前进一格
this.props.history.go()
前进或者后退n格(根据传入的数字正负数)
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参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递search参数 */}
{/* {msgObj.title} */}
{/* 向路由组件传递state参数 */}
<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} />
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
//记得detail组件中也要响应修改接收的参数形式
//news组件中用编程式路由导航控制路由跳转
componentDidMount() {
setTimeout(() => {
this.props.history.push('/home/messages')
}, 2000)
}
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件
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>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
前端路由的原理就是:点击路由链接引起路径的变化,被路由器检测到,多次点击留下历史记录
备注:HashRouter可以用于解决一些路径错误相关的问题。即在
问题6
中引入文件时可以不进行路径修改
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
但一般来说都用的这个
HashRouter使用的是URL的哈希值
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
BrowserRouter没有任何影响,因为state保存在history对象中
HashRouter刷新后会导致路由state参数的丢失!!!
,因为HashRouter不用history这个API