单页面应用(SPA), 前端路由变化不会刷新页面, 不会向服务器发送请求
路由分为前端路由和后端路由, 前前后端路由都是一个键值对,
前端(path, component), 一个路由对应一个组件
后端(path, function), 一个路由对应一个处理函数
单页面应用核心在与history, 即BOM下的history, 两种模式, 一种就是history模式, 另一种是hash模式
前端路由变化可以被监听到, 因此可以写上对应的component
初步编写
app中
import React, { Component } from 'react'
import { Route, Link, NavLink } from 'react-router-dom'
import Hello from './components/Hello'
import World from './components/World'
import './index.css'
export default class App extends Component {
render () {
return (
<div>
// Link以及Route需要包含在Router里面, 有两种Router, 一个是BrowserRouter, 一个是HashRouter
// 分别对应history和hash模式, 可以在这里包裹, 也可以在index.js中挂载时就包裹
/* link不用多说
使用link不会在对应的路由自动加上类名
<Link className="a" to="/Hello">Hello</Link><br />
<Link className="a" to="/World">World</Link>
*/
// 可以使用NavLink, 在对应路由会默认加上active类, 可以定义这个类对应的样式以区别
// 也可以自定义根据路由加上什么类名, 采用activeClassName=""来定义
<NavLink activeClassName="b" className="a" to="/Hello">Hello</NavLink><br />
<NavLink activeClassName="b" className="a" to="/World">World</NavLink>
// 这里不是view什么的, 就是一个路由, 有对应的(path, component)
<Route path="/Hello" component={Hello}></Route>
<Route path="/World" component={World}></Route>
</div>
)
}
}
将router壳包在index.js中
// 入口文件就是引入 + 挂载App实例
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
// history模式
// import { BrowserRouter } from 'react-router-dom'
// hash模式
import { HashRouter } from 'react-router-dom'
// ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'))
// 也可以采用Hsah模式
ReactDOM.render(<HashRouter><App /></HashRouter>, document.getElementById('root'))
路由组件能接受到数据, 封装NavLink, 标签体也是属性(children), 直接写children就可以显示为标签体
如果有多个相同路由都会匹配的情况下, 需要包裹在Switch块中, 这样匹配到一个后就会停止不继续匹配
(默认是继续向下匹配)
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
histroy模式下, 多级路由刷新样式丢失
localhost: 3000表示的路径 === public 及public为根路径, 找不到的话会返回index.html, 只访问public(localhost: 3000)会返回index.html
原因: 会以最低一级路由的./请求, 将前面级也当做请求路径
解决1, ./中去掉 . , 直接就/去找根路径
解决2, 引入时使用%PUBLIC_URL%
解决3, 使用hash
路由匹配方式
在link中的to, 会与route中的路由进行匹配
/home 和 /home/a/b 不能匹配(link ==> route精准匹配, 不能给错, 不能给少)
/home/a/b 和/home 匹配(link ==> route模糊匹配, 可以给多, 不能给错, 先下面那样)
默认是模糊匹配,按顺序比对, 开始不匹配后面就不会匹配了, 如/a/home/b不匹配
注册路由时, **exact = {true}**开启精准匹配, 或直接exact即可, 一般不开启
Redirect
与vue有些许差别, 这个是用来兜底的, 表示都没匹配到的时候, 去某个路由
<div>
<MyLink className="a" to="/Hello">Hello</MyLink><br />
<MyLink className="a" to="/World">World</MyLink>
<Route path="/Hello" component={Hello} />
<Route path="/World" component={World} />
{/* 当匹配不到任何路由时, 可以用这个兜底, 这是用的是to不是path */}
<Redirect to="/Hello" />
</div>
路由传参
params
link中传递的参数路由在注册时需要接收
注册路由时冒号声明后才能接收, 值在路由组件props中的match中parms中
<MyLink to={`/home/message/${message[0].id}/${message[0].tittle}`}>message001</MyLink>
<MyLink to={`/home/message/${message[1].id}/${message[1].tittle}`}>message002</MyLink>
<MyLink to={`/home/message/${message[2].id}/${message[2].tittle}`}>message003</MyLink>
{/* 上面既然传值了, 这里就匹配路由的时候就需要接收 */}
<Route path="/home/message/:id/:tittle" component={Detil} />
在Detil路由组件中取值
render () {
const { id, tittle } = this.props.match.params
console.log(this.props)
return (
<div>
<h3>这是第{id}</h3>
<h3>{tittle}</h3>
</div>
)
}
search参数
也可以采用?的方式进行search方式传参(query参数)
传参
<MyLink to={`/home/message/?id=${message[0].id}&tittle=${message[0].tittle}`}>message001</MyLink>
接收是不需要声明(传递的时候已经声明了)
传过去的值在组件对象里的props.location.search中, 但是这是urlencode编码的形式
需要借助querystring库(不需要安装)
import qs from 'querystring'
qs.stringify(对象) // 将一个对象转化为urlencode编码的格式
qs.parse(Str) // 将一个urlencode格式字符串转化为一个对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGt2ixpw-1628613664309)(https://secure.wostatic.cn/static/3XWnXbRZxmMUvK81PE7wBV/image.png)]
state参数
声明路由时可以为一个对象, 里面的路由key是pathname, 利用这个可以传递state对象
<MyLink to={{ pathname: '/home/message', state: { id: 1, tittle: '我是1消息' } }}>message001</MyLink>
注册路由同样不需要声明接收
传过去的值在组件对象里的props.location.state中, url上不展示, 即使刷新了也还存在, 因为histroy对象在维护location, location虽然是props的属性, 但是也是histroy的属性
注意如果清空缓存, 会丢失, 所以接收时可以写成 xxx = props.location.state || {}
路由嵌套
根路由组件
<div>
<MyLink className="a" to="/About">About</MyLink><br />
<MyLink className="a" to="/Home">Home</MyLink>
<Route path="/About" component={About} />
{/* 精准匹配模式不能随便开启, 这种当有子路由并且子路由重定向时, 就会匹配不到子路由
因为这里点击home后, 此时的匹配模式是home/message(因为home路由下子路由的重定向)
但是此时home组件里的路由还未注册, 只能在这里匹配, 这里只能匹配到home, 但是因为精准匹配
home不会被匹配 */}
<Route path="/Home" component={Home} />
{/* 当匹配不到任何路由时, 可以用这个兜底 */}
<Redirect to="/About" />
</div>
一级子路由组件
render () {
return (
<div>
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
<li>
<MyLink to="/home/news">news</MyLink>
</li>
<li>
<MyLink to="/home/message">message</MyLink>
</li>
</ul>
{/* 注册路由 */}
{/* 嵌套路由需要写全,上一级的路由也要带上 */}
<Route path="/home/message" component={Message} />
<Route path="/home/news" component={News} />
<Redirect to="/home/message" />
</div>
</div>
)
编程路由导航
history下的push和replace方法, 两个参数, 一个path, 一个是state参数
二者方法使用上是一致的, 只是压栈的方式不同, 这里只列举一个
// 点击后进行路由变化
<button onClick={() => { this.toThree(message[2].id, message[2].tittle) }}>消息3</button>
// 定义的函数
toThree = (id, tittle) => {
// 传递search参数
// this.props.history.push(`/home/message/?id=${id}$tittle=${tittle}`)
// 传递params参数, 需要声明接收
// this.props.history.push(`/home/message/${id}/${tittle}`)
// 传递state参数, 第二个参数就是用来传递state的
// this.props.history.push('/home/message', { id, tittle })
}
withRouter, 一般组件用路由组件的方法, 暴露withRouter(Header)
import React, { Component } from 'react'
// 一般组件是不能使用history对象的方法的, 也就是不能对路由进行操作
// 使用的话需要借助react-router-dom下的withRouter, 并且将这个组件向外暴露为处理过的
import { withRouter } from 'react-router-dom'
// 不能这么暴露组件, 需要暴露一个被withRouter处理过后的组件才能操作路由
// export default class index extends Component {
// goback = () => {
// this.props.history.goBack()
// }
// render () {
// return (
// <div>
// <button onClick={this.goback}>后退</button>
// </div>
// )
// }
// }
class Header extends Component {
goback = () => {
this.props.history.goBack()
}
render () {
return (
<div>
<button onClick={this.goback}>后退</button>
</div>
)
}
}
export default withRouter(Header)
路由总结
vue有单独的路由配置文件, react目前了解到的还没有, 对路由的操作, 编程式也好, 声明式也好, 都是在组件内定义的
都是都是react的history对象对路由进行操作的
BrowserRouter是根据H5的history(不同于react路由下的history)的api进行封装的
HashRouter 使用的是url的哈希值
后者在state传参下, 刷新时会丢失数据
- 底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
- path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
- 备注:HashRouter可以用于解决一些路径错误相关的问题。