前四章在这里:React学习记录 ---第一章-CSDN博客
React路由
前置知识
SPA
1.单页Web应用(single page web application,SPA)
2.整个应用只有一个完整的页面
之前是靠切换页面实现路由切换,比如:http://127.0.0.1/index.html,http://127.0.0.1/home.html,这是多页面应用;
{/* 原生html中,靠<a>跳转不同的页面 */}
<a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item" href="./home.html">About</a>
而单页面应用是切换组件实现路由切换,单页面多组件,比如上来是http://127.0.0.1,我点击Home,路由会变成http://127.0.0.1/home,每个路径对应的是一个组件
import { Link, BrowserRouter } from 'react-router-dom'
{/* 在React中靠路由连接实现切换组件 */}
<BrowserRouter>
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
.
.
.
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</BrowserRouter>
3.点击页面中的链接不会刷新页面,只会做页面的局部更新
4.数据都需要通过ajax请求获取,并在前端异步展现
路由
- 一个路由就是一个映射关系(key:value)
key-----value
path-----component / function
路由分类
- 后端路由:
- 理解: value是function, 用来处理客户端提交的请求。
- 注册路由: router.get(path, function(req, res))
- 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
举例如下:
app.get("/search/users",function(req,res){
const {q} = req.query
axios({
url:'http://127.0.0.1',
params:{q}
}).then(response =>{
res.json(response.data)
})
})
2.前端路由:
- 浏览器端路由,value是component,用于展示页面内容。
- 注册路由: <Route path="/test" component={Test}>
- 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
前端路由原理
方式一,直接使用H5推出的history身上的API
let history = History.createBrowserHistory()
方式二,hash值(锚点),路由带#,兼容性比较好
let history = History.createHashHistory()
history.push(path)
history.replace(path)
history.goBack(path)
history.forward(path)
浏览器的历史记录是一个栈的结构
history.push
用于在浏览器的历史堆栈中添加一个新的条目,可以改变浏览器的 URL 并导航到一个新路径,同时不会重新加载页面;有痕
history.replace 不是压入栈顶,而是将栈顶路由替换;无痕
history.forward 是用来前进到浏览器历史记录中的下一个页面,就像用户点击了浏览器的前进按钮一样
react-router-dom
react-router库有三种实现,分别给三种场景使用,分别是web、native和any,这里我们学习web场景的库:react-router-dom
react的一个插件库,专门用来实现一个SPA应用,基于react的项目基本都会用到此库
react-router-dom相关API(@5)
内置组件--7个
Router(路由器)分两种:BrowserRouter和HashRouter(带#)
Route(路由)
路由的基本使用----+4
- <BrowserRouter>
- <HashRouter>
- <Route>
- <Link>
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
App.jsx
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
4.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
路由组件与一般组件的区别
1.写法不同:
一般组件:<Demo />
路由组件:<Route path="/demo" component={Demo} />
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收路由器传递给的三个固定的属性:history,location,match
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"
<NavLink>---+1
NavLink替换的是Link的位置,NavLink可以实现路由链接的高亮,通过activeClassName指定样式名。
封装 NavLink 组件
//未封装
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
//封装后
<MyNavLink to="/about">About</MyNavLink>
其中About是通过this.props上的children属性带过去的,标签体内容是一个特殊的标签属性
components/MyNavLink/index.jsx
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
)
}
}
<Switch>---+1
通常情况下,path和component是一一对应的关系,一对多会导致效率低下
Switch可以提高路由匹配效率(单一匹配)
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
如上被包裹之后,就不会进行重复匹配了
解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter(少见)
路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
比如/home---/home/a/b可以, ---/a/home/b不对,顺序不对
2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
或者简写为<Route exact path="/about" component={About}/>
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由。
<Redirect>---+1
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由
1.注册子路由时要写上父路由的path值 path=“/home/news”, path=“/home/message”,
2.路由的匹配是按照注册路由的顺序进行的
如果给/home开启严格模式,他后面的子路由就全废了
向路由组件传递参数
1.params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
或<Link to={` /demo/test/${name}/${age} `}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
组件内接收参数:this.props.match.params
使用场景最多
2.search参数
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
或<Link to='/demo/test?name=${name}&age=${age}'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
import qs from 'querystring'
let obj = {name: 'tom', age: 18}
qs.stringify(obj) //name=tom & age=18
let str = 'carName=奔驰&price=99'
qs.parse(str) //{carName: '奔驰', price: 99}
提取search参数,不能直接使用,需要截取掉问号+转成对象,才能使用!
//接受search参数
const {search} = this.props.match.params
const {id,title} = qs.parse(search.slice(1))//去掉?
使用场景较多
3.state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{id:'01',title:'消息'}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数
由于我们使用的是BrowserRouter,他身上的history对象帮我们记着呢
应用场景:想要携带一些参数过去,但又不想让用户知道,比如一个人的手机号
编程式路由导航
编程式路由导航和正常写路由链接没啥区别,其实玩得就是history身上的API
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.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参数 */}
{/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递search参数 */}
{/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递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参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* search参数无需声明接收,正常注册路由即可 */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* 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>
)
}
}
withRouter
如何让一般组件也能用上路由组件的API呢?
(ps:vue不分一般组件和路由组件,其实例对象上都有这些方法)
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)
加工前:
加工后:
BrowserRouter与HashRouter的区别
1.底层原理不一样:
BrowserRouter底层调用的是H5的history API,this.props.history是react对他的二次封装,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。/#/a/b/c,#后面的部分不会发给服务器
例如,如果你的URL是这样的:
http://www.example.com/#/home
这里的 #/home
是哈希部分,它告诉 HashRouter
应该渲染哪个组件(在这个例子中是主页)。但是,当这个请求发送到服务器时,服务器实际上只接收到:
http://www.example.com/
服务器不会看到 #/home
部分,因此也不会处理它
总结:1、如果你需要服务器能够访问路由参数,你可能需要使用其他方法,比如 BrowserRouter
,它使用HTML5的history API来实现路由,并且可以向服务器发送完整的URL路径。但是,使用 BrowserRouter
时,服务器必须配置以正确处理不同的路径
2、尽管HashRouter的兼容性比BrowserRouter的兼容性好,但一般使用react开发,浏览器不会低于IE9。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题,比如上面提到过的样式丢失
关注我,不迷路!
会不定时为大家更新优质内容!收藏起来方便随时查看!