中文文档:React Router: Declarative Routing for React.js
英文:Declarative routing for React apps at any scale | React Router
1. 相关理解
1.1. SPA的理解
- 单页Web应用(single page web application,
SPA
) - 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过
ajax
请求获取, 并在前端异步展现。
补充:react的路由是用浏览器BOM的history实现,实现路径的前进后退替换。
history有两种工作模式:
第一种模式是:直接使用H5推出的history身上的API
第二种模式是:Hash值(锚点),就像锚点跳转
前端路由原理
一般借助 history.js管理
https://cdn.bootcss.com/history/4.7.2/history.js
1、前端路由主要依靠BOM的 api history(浏览器的历史记录) 不会引发页面刷新
2、let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API 创建 history对象
3、let history = History.createHashHistory() //方法二,hash值(锚点)
4、history.listen 监听路径变化
5、history.push 在浏览历史记录添加一条数据 点击前进后退一条历史记录
6、浏览器的历史记录是栈结构 (历史记录是进栈出栈)
7、history.replace(path) 替换上一条历史记录
8、history.goBack()后退 history.goForward()前进
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
<script type="text/javascript">
// let history = History.createBrowserHistory() //方法一,直接使用H5推出的history身上的API
let history = History.createHashHistory() //方法二,hash值(锚点)
//在浏览历史记录添加一条数据
function push(path) {
history.push(path)
return false//禁止 A标签跳转
}
//替换上一条历史记录
function replace(path) {
history.replace(path)
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
//监听变化
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
</script>
1.2. 路由的理解
1.2.1 什么是路由?
- 一个路由就是一个映射关系(
key: value
) key
为路径,value
可能是function
或component
1.2.2 路由分类
- 理解:
value
是function
, 用来处理客户端提交的请求。 - 注册路由:
router.get(path, function(req, res))
- 工作过程:当
node
接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
2. 前端路由
- 浏览器端路由,value是
component
,用于展示页面内容。 - 注册路由:
<Route path="/test" component={Test}>
- 工作过程:当浏览器的
path
变为/test
时, 当前路由组件就会变为Test
组件
1.3. react-router-dom 的理解
React
的一个插件库。- 专门用来实现一个SPA应用。
- 基于
React
的项目基本都会用到此库。
你所安装的库并不是react-router,因为react-router这个库有三种实现,给三种平台去用,第一种是web网页开发人员,第二种是native,是给写react native的人用的,第三种是any,在哪种都能用。我们学的是第一种web用的react-router-dom。
2. react-router-dom相关API
2.1. 内置组件
<BrowserRouter>
<HashRouter>
<Route>
<Redirect>
<Link>
<NavLink>
<Switch>
2.2. 其它
history
对象match
对象withRouter
函数
3. 基本路由使用
3.1. 效果
3.2. 准备
- 下载react-router-dom:
npm install react-router-dom
2、引入bootstrap.css: <link rel="stylesheet" href="/css/bootstrap.css">
3.3 路由的基本使用
1、明确好界面中的导航区、展示区
2、导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
3、展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
4、<App>的最外侧包裹了一个<BrowserRouter>(history对象)或<HashRouter>(hash值(#号后面的值)的切换,不会发给服务器)
3.4 实现
index.js
这里用一个标签将整个App包起来,保证使用的是同一个路由器
//引入react核心库
import React from 'react'
//引入ReactDOM
import ReactDOM from 'react-dom'
//
import {BrowserRouter} from 'react-router-dom'
//import {HashRouter} from 'react-router-dom'
//引入App
import App from './App'
//把BrowserRouter换成HashRouter后链接就会变成井号的形式
/*
例如:
1、BrowserRouter
链接:localhost:3000/about
2、HashRouter
链接:localhost:3000/#/about
*/
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 './Home'
import About from './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">
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在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>
</div>
)
}
}
components/About/index.jsx
import React,{Component} from "react";
export default class About extends Component {
render(){
return (
<h3>我是About的内容</h3>
)
}
}
components/Home/index.jsx
import React,{Component} from "react";
export default class Home extends Component {
console.log(this.props)
render(){
return (
<h3>我是Home的内容</h3>
)
}
}
3.5 路由组件与一般组件的区别
1、写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
2、存放位置不同:
一般组件:components
路由组件:pages
3、接收到的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"
3.6 NavLink与封装NavLink
NavLink可以实现路由链接的高亮,通过activeClassName
属性指定样式名,默认是"active"
<NavLink activeClassName="demo" className="list-group-item" to="/home">Home</NavLink>
可以自己封装一个NavLink【一般组件】
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
// console.log(this.props);
return (
<NavLink activeClassName="demo" className="list-group-item" {...this.props} />
)
}
}
标签体内容是特殊的标签属性通过this.props.children可以获取标签体内容
使用
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
3.7 Switch的使用
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
这样只要匹配到了第一个就不会再往下匹配了
前提是需要引入Switch组件
import { Link,Route,Switch } from 'react-router-dom'
3.8 解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写
./
写/
(常用)【绝对路径】 - public/index.html 中 引入样式时不写
./
写%PUBLIC_URL%
(常用) - 使用
HashRouter
路由路径是多级的时候,第一次打开没问题,只要一刷新样式就会丢失,原因是它认为多级路径也是样式的路径的一部分。解决办法有三个,就是上面的那三个。
请求的路径不存在,会把public下的index.html返回。
例如请求的样式文件不存在,那么就会返回public下的index.html
3.9 路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}/>或<Route exact path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
3.10 Redirect的使用 【重定向】
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
- 具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
4. 嵌套路由使用
4.1 效果
4.2 注意
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
4.3 实现
Home/index.jsx
import React, { Component } from 'react'
import { Route, NavLink,Redirect,Switch } from 'react-router-dom'
import News from './News'
import Message from './Message'
export default class Home extends Component {
render() {
return (
<div>
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
<li>
<NavLink className="list-group-item" to="/home/news">News</NavLink>
</li>
<li>
<NavLink className="list-group-item" to="/home/message">Message</NavLink>
</li>
</ul>
<Switch>
<Route path='/home/news' component={News} />
<Route path='/home/message' component={Message} />
<Redirect to='/home/news' />
</Switch>
</div>
</div>
)
}
}
5. 向路由组件传递参数数据
5.1 效果
扫盲ajax请求时携带参数的方式
params:/demo/test/tom/18
query:?name=tom&age=18
body :编码格式 urlencode格式 JSON格式
【urlencoded格式】:
又叫form格式,或者是x-www-form-urlencoded格式。
口诀:表单格式是由键值对组成。键和值之间用=。多个键值对之间用&。例如:name=ZhangSan&age=16
【json 格式】:
口诀1:JSON有对象、数组两种结构,有字符串、数字、逻辑值、空值四种数据类型
口诀2:用大括号{}表示对象。对象是由属性组成的。属性是由键值对组成的。键和值之间用冒号隔开。属性之间用逗号隔开。键必须用双引号引起来。
口诀3:用中括号[] 表示数组。数组由单独的值组成
口诀4:JSON的灵活就灵活在:JSON可以嵌套
5.2 具体方法
方法1. params参数
1、路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
2、注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
3、接收参数:this.props.match.params
Message/index.jsx
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' },
]
}
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/index.jsx
import React, { Component } from 'react'
export default class Detail extends Component {
state = {
detailData : [
{ id: '01', content: '你好啊' },
{ id: '02', content: '还不错鸭' },
{ id: '03', content: '显示我吧' }
]
}
render() {
console.log(this.props)
// 接收params参数
const { id, title } = this.props.match.params
const findResult= this.state.detailData.find((dataObj) => {
return dataObj.id === id
})
return (
<div>
<ul>
<li>ID: {id }</li>
<li>Title: {title }</li>
<li>Content: { findResult.content}</li>
</ul>
</div>
)
}
}
方法2. search参数
1、路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
2、注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
3、接收参数:this.props.location.search
4、备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
qs库 (querystring)无需下载,react已经包含了
qs.stringify 转为urlencoded编码字符串 name=tom&age=18
qs.parse 转为对象
import qs from 'querystring'
let obj = {name:'tom', age:18}
console.log(qs.stringify(obj)) // name=tom&age=18
let str = 'carName=Benz&price=199'
console.log(qs.parse(str)) // {carName: 'Benz', price: 199}
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
{/* search参数无需声明接收,正常注册路由即可 */}
<Route path="/home/message/detail" component={Detail}/>
-----------------------------------
import qs from 'querystring'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,世界'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收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>
)
}
}
方法3. state参数
1、路由链接(携带参数):<Link to={{ pathname:'/demo/test', state:{name:'tom',age:18} }}>详情</Link>,第一外层的花括号只是说明是写js表达式的,第二层的花括号才说明是一个对象。
2、注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
3、接收参数:this.props.location.state
4、备注:刷新也可以保留住参数【history对象记录着在】
state方式不会把参数在地址栏中传递,是隐藏的。
state的优点就是地址栏没有传递的参数,是隐藏的
代码
export default class Message extends Component {
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>
</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}/>
</div>
)
}
}
Detail/index.jsx
import React, { Component } from 'react'
// import qs from 'querystring'
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
// const {id,title} = this.props.match.params
// 接收search参数
// const {search} = this.props.location
// const {id,title} = qs.parse(search.slice(1))//因为开始有一个问号所以search.slice(1),例如 ?id=01&title=消息一
// 接收state参数
const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{
return detailObj.id === id
}) || {} //如果查不到就会返回undefined,加上一个{}就返回空对象了
return (
<ul>
<li>ID:{id}</li>
<li>TITLE:{title}</li>
<li>CONTENT:{findResult.content}</li>
</ul>
)
}
}
push与replace模式
路由跳转的两种模式:
push:是一个压栈的操作会留下痕迹,默认路由跳转用的是push模式,每一次路由跳转都会留下痕迹,每一次前进后退都会有历史记录 ,展示的是栈顶的一条(默认push)
replace :是替换掉目前的这个也就是替换掉栈顶的 <MyNavLink replace to="/about">About</MyNavLink>
replace跳转会替换掉当前的:
6. 多种路由跳转方式
6.1 编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
- this.prosp.history.push()
- this.prosp.history.replace()
- this.prosp.history.goBack()
- this.prosp.history.goForward()
- this.prosp.history.go()
history的api
this.props.history.goBack()后退
this.props.history.goForward() 前进
this.props.history.go(-2)指定前进或者后退几条历史几录
this.props.history.replace 替换历史记录跳转
this.props.history.push保存历史记录跳转
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>
)
}
}
import React, { Component } from 'react'
// import qs from 'querystring'
const DetailData = [
{id:'01',content:'你好,中国'},
{id:'02',content:'你好,世界'},
{id:'03',content:'你好,未来的自己'}
]
export default class Detail extends Component {
render() {
console.log(this.props);
// 接收params参数
// const {id,title} = this.props.match.params
// 接收search参数
// const {search} = this.props.location
// const {id,title} = qs.parse(search.slice(1))
// 接收state参数
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>
)
}
}
6.2 withRouter的使用
export default withRouter(Header)
withRouter
可以加工一般组件,让一般组件具备路由组件所特有的APIwithRouter
的返回值是一个新组件
在一般组件暴露额时候用withRouter
在Header组件中就可以使用下面的API了:
this.props.history.goBack()后退
this.props.history.goForward() 前进
this.props.history.go(-2)指定前进或者后退几条历史几录
this.props.history.replace 替换历史记录跳转
this.props.history.push保存历史记录跳转
一般组件的方式应用组件:<header/>
7. 注意
BrowserRouter与HashRouter的区别
1、底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2、path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3、刷新后对路由state参数的影响
(1) BrowserRouter没有任何影响,因为state保存在history对象中。
(2) HashRouter刷新后会导致路由state参数的丢失!!!
4、备注:HashRouter可以用于解决一些路径错误相关的问题。
https://blog.csdn.net/weixin_44972008/article/details/117856718
【React】SPA - 路由机制 - react-router-dom - 基本路由 - 嵌套路由 - 传递参数 - 路由跳转_YK菌的博客-CSDN博客
印记中文,有好多前端的中文文档https://docschina.org/