React路由
1.什么是路由
一个路由其实就是一个映射关系(k:v)
key为路径,value可能是function 或者是 component
(1)后端路由:
value是function,用来处理客户端提交的请求
注册路由:router.get(path,function(req,res))
工作过程:当node接收一个请求的时候,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应的数据
(2)前端路由:
浏览器端路由,value是Component,用于展示页面内容
注册路由:< Route path="/test" component={Test}>
工作过程:当浏览器的path变为/test的时候,当前路由组件就会变成Test组件
(3)前端路由的原理
这个主要是依靠于history,也就是浏览器的历史记录。
浏览器上的记录其实就是一个栈,前进一次就是入栈,后退一次就是出栈。
并且历史记录上有一个监听的方法,可以时时刻刻监听记录的变化。从而判断是否改变路径
SPA
单页Web应用(single page web application,SPA)。整个应用只有一个完整的页面。
点击页面中的链接不会刷新页面,只会做页面的局部更新。
数据都需要通过ajax请求获取,并在前端异步展现
2.使用 react-router
(1) 安装与使用
在项目文件夹中打开命令行窗口进行 npm
下载
npm i react-router-dom -S
复制代码
注意:我们下载的是 react-router-dom
而不是 react-router
两者区别:
react-router
:提供了router
的核心 API。如Router
、Route
、Switch
等,但没有提供有关dom操作进行路由跳转的API;react-router-dom
:提供了BrowserRouter
、Route
、Link
等api,可以通过dom操作触发事件控制路由。
react-router-dom
中包含了react-router
,所以我们选择下react-router-dom
。
(2) 常用组件
a. 路由跳转
在多页面应用中,通常都是使用 a
标签进行页面跳转
<a href="http://localhost:3000">跳转页面</a>
复制代码
使用单页面富应用中使用react-router
则使用路由跳转组件
import {Link, NavLink} from "react-router";
复制代码
<Link activeClassName="nav-active" className="nav" to="/about">About</Link>
<NavLink activeClassName="nav-active" className="nav" to="/home">Home</NavLink>
复制代码
activeClassName
: 处于当前路由时,对应的组件会自动添加该类className
: 当前组件类名to
: 当前组件所对应的路由
Link
组件与 NavLink
组件都可以进行路由的跳转,区别在于:当前路由对应的NavLink
会自动添加class
: active
,而 Link
不会。
b. 注册路由
import {Route} from "react-router";
复制代码
<Route path="/home" component={Home}></Route>
<Route exact path="/about" component={About}></Route>
复制代码
path
: 所要监听的路由component
: 该路由要绑定的组件exact
: 可选,不写时为false
,是否选择严格匹配
当当前路由对应上了路由组件所绑定的路由时,则会展示所绑定的组件。
模糊匹配和精准匹配
react默认是开启模糊匹配的。
比如:
<MyNavLink to = "/home/a/b" >Home</MyNavLink>
此时该标签匹配的路由,分为三个部分 home a b;将会根据这个先后顺序匹配路由。
如下就可以匹配到相应的路由:
<Route path="/home"component={Home}/>
但是如果是下面这个就会失败,也就是说他是根据路径一级一级查询的,可以包含前面那一部分,但并不是只包含部分就可以。
<Route path="/a" component={Home}/>
当然也可以使用这个精确的匹配 exact={true}
如以下:这样就精确的匹配/home,则上面的/home/a/b就不行了
<Route exact={true} path="/home" component={Home}/>
或者
<Route exact path="/home" component={Home}/>
c. 重定向路由
你明明设置好了路由 /home
,但是有的用户就喜欢对着干,在地址栏输入了 /nothing
,而你没有注册这个路由,那该怎么办呢?
这个时候你就可以东涌道重定向路由了,对于没有注册过的路由,都会被跳转到你指定的某个路由去,这就是重定向路由。
经常可以用作一些404,页面丢失等情况的路由跳转方式。
import {Redirect, Route} from "react-router";
复制代码
<Route ....../>
<Route ....../>
<Redirect to="/home"/>
复制代码
to
: 需要重定向到哪个路由?
Redirect
需放在所有Route
下面,当上面的 Route
都没有匹配到时,则路由将重定向到指定的路由。
d. Switch 路由
你想想,如果你的路由中,出现了 /home
与 /home/abc
和 /home/a/b/c
等这样的路由,当路由为 /home
时则会三个路由都同时渲染,但是你又只想要渲染其中的一条,这个时候我们就可以使用 Switch
组件。
使用 Switch
组件包裹住所有的 Route
和 Redirect
,当出现多个匹配的路由时,只会渲染第一个匹配的组件。
import {Switch, Route, Redirect} from "react-router";
复制代码
<Switch>
<Route ..../>
<Route ..../>
<Redirect to="..."/>
</Switch>
e. 路由器
你想要使用路由跳转组件和路由组件,还差一个路由器组件,同时路由器组件必须包裹着这两个组件。
import {HashRouter, BrowserRouter} from "react-router";
复制代码
一般为了使整个React应用都可以使用到路由组件,所以一般我们都是把路由器包裹在根组件上的。
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.querySelector("#root")
);
复制代码
有两种路由器组件,分别是HashRouter
与 BrowserRouter
,分别对应者两种路由方式
(3)路由组件以及一般组件
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
因为Link不能够改变标签体,因此只适合用于一些写死的标签。而如果想要有一些点击的效果,使用NavLink.
如下代码,就写了ctiveClassName,当点击的时候就会触发这个class的样式
{/*NavLink在点击的时候就会去找activeClassName="ss"所指定的class的值,如果不添加默认是active
这是因为Link相当于是把标签写死了,不能去改变什么。*/}
<NavLink ctiveClassName="ss" className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
但是可能一个导航又很多标签,如果这样重复的写NavLink也会造成很多的重复性的代码问题。
因此可以自定义一个NavLink:
// 通过{...对象}的形式解析对象,相当于将对象中的属性全部展开
//<NavLink to = {this.props.to} children = {this.props.children}/>
<NavLink className="list-group-item" {...this.props}/>
在使用的时候:直接写每个标签中不一样的部分就行,比如路径和名称
{/*将NavLink进行封装,成为MyNavLink,通过props进行传参数,标签体内容props是特殊的一个属性,叫做children */}
<MyNavLink to = "/about" >About</MyNavLink>
3.嵌套路由
简单来说就是在一个路由组件中又使用了一个路由,就形成了嵌套路由。
举个例子来说:
我们在home这个路由组件中又添加两个组件:
APP.jsx:
<Route path="/home" component={Home}/>
Home.jsx:
<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/message"/>
</Switch>
</div>
react中路由的注册是有顺序的,因此在匹配的时候也是按照这个顺序进行的,因此会先匹配父组件中的路由
比如上面的 /home/news的路由处理过程:
1.因为父组件home的路由是先注册的,因此在匹配的时候先去找home的路由,也就是根据/home/news先模糊匹配到/home
2.在去Home组件里面去匹配相应的路由,从而找到了/home/news进行匹配,因此找到了News组件。
但是如果开启精确匹配,就会在第一步的时候卡住,这个时候就走不下去了。因此不要轻易的使用精确匹配
4. 编程式路由
如果说,我们想要做用户点击按钮登陆后,如果他是老师就去老师页面,如果是学生就去学生页面,这个显然单靠 Link
无法完成,我们可以通过 js
进行路由的跳转(也是 react-router
基于 History API
编写的)
class Message extends Component {
state = {
messageArr:[
{id:"01", title: "消息1"},
{id:"02", title: "消息2"},
{id:"03", title: "消息3"},
]
}
// 编程式路由导航
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});
}
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});
}
// 后退
goBack = ()=>{
this.props.history.goBack();
}
// 前进
goForward = ()=>{
this.props.history.goForward();
}
// 跳转指定位置
go = ()=>{
// 向前两步
this.props.history.go(2);
// 后退两步
this.props.history.go(-2);
}
render() {
const {messageArr} = this.state;
return (
<div>
<ul>
{
messageArr.map(item=>{
return (
<li key={item.id}>
<Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
<button onClick={() => this.pushShow(item.id, item.title)}>push查看</button>
<button onClick={() => this.replaceShow(item.id, item.title)}>replace查看</button>
</li>
)
})
}
</ul>
<hr/>
<Route path="/home/message/detail/:id/:title" component={Detail} />
<button onClick={this.goBack}>goBack</button>
<button onClick={this.goForward}>goForward</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
复制代码
总结一下上面的代码:
- 编程式路由都是通过
props
中的history
对象进行操作(都是该对象身上的方法,调用方式为:this.props.history.xxx
) - 常用方法:
push(route[, state])
: 跳转到指定路由(带有历史记录)replace(route[, state])
: 跳转到指定路由(不带有历史记录)goBack()
: 后退一个goForward()
: 前进一个go(num)
: 前往指定步数,当num
为正数时,为前进,当num
为负数时则为后退。
5. withRouter 组件
有的时候,我们想要在其他组件中也使用路由组件的功能,比如导航栏,应该属于公用组件,但是里面的导航链接的功能却是路由组件的功能,我们应该怎么解决呢?
在 react-router
中,提供了这么一种方法,可以让一般组件具有路由组件的功能,则就是 withRouter()
方法。
看看演示:
import {withRouter} from "react-router-dom";
class Header extends Component {
// withRouter后该组件也有了路由组件的功能
goBack = ()=>{
this.props.history.goBack();
}
go = ()=>{
this.props.history.go(2);
}
goForward = ()=>{
this.props.history.goForward();
}
render() {
return (
<div>
<h1>This is a React-router-dom Test!</h1>
<button onClick={this.goBack}>goBack</button>
<button onClick={this.goForward}>goForward</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
// withRouter 用于给一般组件添加上路由组件特有的功能,返回一个新组件
export default withRouter(Header);