React-Router-Dom@5学习使用
React-Router-Dom@5学习使用
由于v6改动确实太大,而且现有的中文文档是在是不全,迫于无奈还是重新在学习一下v5版本(叹气~)。
安装
由于现在安装会自动默认安装最新的版本(也就是v6),所以在安装的时候,我们需要指定一下安装的版本yarn add react-router-dom@5
路由的跳转实现
原生的HTML中依靠标签跳转不同的页面,在React中则依靠路由链接切换组件Link。
// 原生
<a class="xxxxx" href="xxxxxx" >About</a>
// React
import {Link} from 'react-router-dom'
<Link className="xxxxx" to="">About</Link>
但是按照上面的模式,在引入Link后,还需要Router或BrowserRouter等相互配合进行转发才可以。
路由的引入
BrowserRouter
路径相对自然,以/间隔菜单
HashRouter
路径以#间隔,#号后面的内容都不会以资源发送给服务器
注册路由
在页面写好了链接以后,实际已经实现了页面地址随着点击按钮切换和变化,但是页面的内容并不会根据点击而随之切换。所以还需要将对应的组件注册到路由上。
通常常用的方式,直接将App函数如下包裹,下面也就是一个可以正常使用的样例
# index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
)
# App.js
import React, { Component } from "react";
import { Link,Route } from "react-router-dom";
import Home from "./components/Home";
import About from "./components/About";
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<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>
</div>
);
}
}
路由组件
组件分为一般组件(自己实现的组件)和路由组件。最大的区别还是 接收到的参数问题。除了写法不同外,常规的一般组件,需要我们通过手动去传递参数,才可以通过this.props接收到参数。
# App.js
<Header a={1} />
# Header.jsx
import React, { Component } from "react";
export default class Header extends Component {
render() {
console.log('Header收到的参数是:',this.props)
return <h2>React Router Demo</h2>;
}
}
>>> Header收到的参数是: {a: 1}
而路由组件则不一样,传递的参数如下
# App.js
<Route path="/about" component={About} />
# About.jsx
import React, { Component } from "react";
export default class About extends Component {
render() {
console.log('About:',this.props)
return <div>我是About的内容</div>;
}
}
>>> About: {history: {…}, location: {…}, match: {…}, staticContext: undefined}
路由组件会额外传递history、location、match三个参数。
NavLink使用
由于通常在页面会涉及点击选中等其他样式的问题,所以Link标签无法实现,这是就出现了NavLink标签,NavLink在选中的时候会自动默认给选中的标签添加一个active的样式类。如果需要自定义也可以添加activeClassName属性,也就是自定义选中的类名。
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" activeClassName="myActive" to="/about">About</NavLink>
封装NavLink标签
在使用NavLink样式和参数较多的时候,可以采用封装NavLink的方式来实现。
···javascript
MyNavLink.jsx
import React, { Component } from "react"; import { NavLink } from "react-router-dom";
export default class MyNavLink extends Component {
render() {
return (
App.jsx
# Switch的使用
如果路由数量很多,而且匹配到一个就终止不需要在往下匹配(提升效率),这时就可以使用`Switch`组件。它会按照顺序匹配,而且一般是多个的时候才会用`Switch`包裹。
```javascript
import { Route,Switch } from "react-router-dom";
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Switch>
总结:
+ 通常情况下,path和component是一一对应的关系
+ Switch可以提高路由的匹配效率(单一匹配)
可能存在的样式丢失问题
由于路径的变化问题,可能会出现样式丢失的问题,建议在index.html里面都统一使用%PUBLIC_URL%/...来替代资源路径。
<link href="%PUBLIC_URL%/css/bootstrap.min.css" rel="stylesheet">
模糊匹配&精准匹配
默认是模糊匹配,如果添加exact关键词就是精准匹配。
// 模糊匹配
<Route path="/about" component={About} />
// 精准匹配
<Route exact path="/about" component={About} />
Redirect的使用
Redirect通常放在路由的最下方,用来兜底如果输入链接都未匹配到,则进行跳转Redirect的设置路由上。
import { Route,Switch,Redirect } from "react-router-dom";
<div className="list-group">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
<div className="panel-body">
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
</div>
嵌套路由
多级路由的实现,如果开启了严格匹配则无法进行二级路由匹配。
需要注意下面的代码中,/home是一级路径,而/home/news或者/home/message则是二级路由,路径需要一致并对应。
- 注册子路由的时候要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
<div>
<h3>Home组件内容</h3>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
<ul>
{/* 注册路由 */}
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
<Redirect to="/home/news" />
</Switch>
</ul>
</div>
</div>
给路由组件传递参数
携带params参数传递
最大的特点就是直接展示到路径中。
+ 路由链接(携带参数): <Link to={/home/message/detail/${msgObj.id}/${msgObj.title}}>{msgObj.title}</Link>
+ 注册路由(声明接收): <Route path="/home/message/detail/:id/:title" component={Detail} />
+ 接收参数 : const {id,title} = this.props.match.params
# Message.jsx
import React, { Component } from "react";
import { Link,Route } from "react-router-dom";
import Detail from "./components/Detail";
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
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>
</li>
)
})
}
</ul>
<hr />
{/* 声明接受params参数 */}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
)
}
}
# Detail.jsx
import React, { Component } from "react";
// 消息详情
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,我自己'},
{id:'03',content:'你好,广州'},
]
export default class Detail extends Component {
render() {
// 接受params参数
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>
);
}
}
传递search参数
路由链接(携带参数):<Link to={/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}}>{msgObj.title}</Link>
注册路由(无需声明,正常注册即可):<Route path="/home/message/detail" component={Detail} />
接收参数:const {search} = this.props.location
备注:获取到的search是urlencode编码字符串,需要借助qs库来实现解析
# Message.jsx
import React, { Component } from "react";
import { Link,Route } from "react-router-dom";
import Detail from "./components/Detail";
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map(msgObj=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
{/*
声明接受search参数
search参数无需声明接收,直接正常注册即可
*/}
<Route path="/home/message/detail" component={Detail} />
</div>
)
}
}
# Detail.jsx
import React, { Component } from "react";
import qs from 'qs'
// 消息详情
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,我自己'},
{id:'03',content:'你好,广州'},
]
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参数传递
state属于隐藏传递参数,不会在地址栏展示。但是存在如果分享链接等别人无法打开。类似(POST)请求的模式。
路由链接(携带参数):<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
注册路由(无需声明,正常注册即可):<Route path="/home/message/detail" component={Detail} />
接收参数:const {id,title} = this.props.location.state
备注:本机刷新可以保留,会在缓存记录。
# Message.jsx
import React, { Component } from "react";
import { Link,Route } from "react-router-dom";
import Detail from "./components/Detail";
export default class Message extends Component {
state = {
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'},
]
}
render() {
const {messageArr} = this.state
return (
<div>
<ul>
{
messageArr.map(msgObj=>{
return (
<li key={msgObj.id}>
{/* 向路由组件传递state参数 */}
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
{/*
声明接受state参数
state参数无需声明接收,直接正常注册即可
*/}
<Route path="/home/message/detail" component={Detail} />
</div>
)
}
}
# Detail.jsx
import React, { Component } from "react";
// 消息详情
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,我自己'},
{id:'03',content:'你好,广州'},
]
export default class Detail extends Component {
render() {
// 接收state参数
console.log(this.props);
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>
);
}
}
路由使用总结
整体来说,个人感觉params还是比较经常使用的,如果网页涉及分享等就不要用state方式,对传递数据有隐私要求等可以使用。
路由跳转的模式
系统默认使用的是push模式进行跳转的,所以在页面会留下访问痕迹(栈内存地址)。
replace模式,则只要在Link中添加replact关键字即可。
+ 比如当前页面无权限访问,可以使用replace跳转到无权限页面
+ 或者支付成功后,不让页面后退,避免重复支付等
<Link replace to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
编程式路由导航
借助this.props.history对象上的API进行路由跳转、前进、后退等操作。
+ this.props.history.push()
+ this.props.history.replace()
+ this.props.history.goBack()
+ this.props.history.goForward()
+ this.props.history.go()
import React, { Component } from "react";
import { Link,Route } from "react-router-dom";
import Detail from "./components/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}`)
}
pushShow = (id,title)=>{
// push跳转++携带params参数
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}>
<Link to={`/home/message/detail/${msgObj.id}/${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 />
{/*
声明接受state参数
state参数无需声明接收,直接正常注册即可
*/}
<Route path="/home/message/detail/:id/:title" component={Detail} />
</div>
)
}
}
如果是search参数类型则可以如下。
replaceShow = (id,title)=>{
// replace跳转+携带search参数
this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)
}
pushShow = (id,title)=>{
// push跳转++携带search参数
this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)
}
如果是state传参,也可以如下方式
replaceShow = (id,title)=>{
// replace跳转+携带state参数
this.props.history.replace('/home/message/detail/',{id:id,title:title})
}
pushShow = (id,title)=>{
// push跳转++携带state参数
this.props.history.push('/home/message/detail/',{id:id,title:title})
}
history中的goBack和goForward也是直接支持在页面操作前进、后退功能的。
go则支持自定义跳转页数。
back = ()=>{
this.props.history.goBack()
}
forward = ()=>{
this.props.history.goForward()
}
go = ()=>{
this.props.history.go(2)
}
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>Go</button>
自定义页面打开跳转。
import React, { Component } from "react";
export default class News extends Component {
componentDidMount(){
setTimeout(()=>{
this.props.history.push('/home/message')
},2000)
}
render() {
return (
<ul>
<li>news01</li>
<li>news02</li>
<li>news03</li>
</ul>
);
}
}
withRouter 使用
一般组件是无法进行跳转的。因为没有对应的路由传递的三大参数。可以使用withRouter解决一般组件具备路由组件特有的API。
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();
};
render() {
return (
<div>
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
</div>
);
}
}
export default withRouter(Header)
BrowserRouter和HashRouter的区别
- 底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。HashRouter使用的是URL的哈希值。 - url表现形式不一样
BrowserRouter的路径中没有#,比如: localhost:3000/demo/testHashRouter的路径包含#,比如: localhost:3000/#/demo/test - 刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中HashRouter刷新后会导致state参数丢失 HashRouter可以用于解决一些路径错误相关的问题。