React 路由(5.x版本)
- 一、相关理解
-
- [1、SPA 的理解](#1、SPA 的理解)
- 2、路由的理解
-
- [2.1 什么是路由?](#2.1 什么是路由?)
- [2.2 路由分类](#2.2 路由分类)
-
- [2.2.1 后端路由](#2.2.1 后端路由)
- [2.2.2 前端路由](#2.2.2 前端路由)
- [3、react-router-dom(Web 开发使用) 的理解](#3、react-router-dom(Web 开发使用) 的理解)
- [二、react-router-dom 相关 API](#二、react-router-dom 相关 API)
-
- 1、内置组件
-
- [1.1 BrowserRouter](#1.1 BrowserRouter)
- [1.2 HashRouter](#1.2 HashRouter)
- [1.3 Route](#1.3 Route)
- [1.4 Redirect](#1.4 Redirect)
- [1.5 Link](#1.5 Link)
- [1.6 NavLink](#1.6 NavLink)
- [1.7 Switch](#1.7 Switch)
- 2、路由的基本使用
- 3、路由组件和一般组件
-
- [3.1 路由组件](#3.1 路由组件)
- [3.2 一般组件](#3.2 一般组件)
- 4、解决多级路由下页面刷新样式丢失问题
- 5、路由的模糊匹配与精确匹配
-
- [5.1 匹配的路径需要的和顺序都对上,默认就是模糊匹配](#5.1 匹配的路径需要的和顺序都对上,默认就是模糊匹配)
- [5.2 精确匹配 使用 exact 属性](#5.2 精确匹配 使用 exact 属性)
- [5.3 如果模糊匹配引发异常,才开启 精确匹配,一般不会全部开启精确(严格)匹配](#5.3 如果模糊匹配引发异常,才开启 精确匹配,一般不会全部开启精确(严格)匹配)
- [5.4 总结](#5.4 总结)
- [6、Redirect 的使用](#6、Redirect 的使用)
- 7、二级路由(嵌套路由)
- 8、向路由组件传递参数
-
- [8.1 params 参数](#8.1 params 参数)
- [8.2 search 参数](#8.2 search 参数)
- [8.3 state 参数(路由组件独有的 state)](#8.3 state 参数(路由组件独有的 state))
- [9、push 和 replace](#9、push 和 replace)
- 10、编程式路由导航
- [11、withRouter 的使用](#11、withRouter 的使用)
- [12、BrowserRouter 与 HashRouter 的区别](#12、BrowserRouter 与 HashRouter 的区别)
一、相关理解
1、SPA 的理解
1、单页 Web 应用(single page web application, SPA)
2、整个应用只有一个完整的页面
3、点击页面中的链接不会刷新 页面,只会做页面的局部更新
4、数据都需要通过 ajax 请求获取,并在前端异步展现
单页面,多组件
2、路由的理解
2.1 什么是路由?
1、一个路由就是一个映射关系(key:value)
2、key 为路径,value 可能是 function 或 component
2.2 路由分类
2.2.1 后端路由
1、理解: value 是 function ,用来处理客户端提交的请求。
2、注册路由:router.get(path,function(req,res))
3、工作过程:当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
2.2.2 前端路由
1、浏览器端路由:value 是 component, 用于展示页面内容
2、注册路由:
3、工作过程:当浏览器的 path 变成 /test 时,当前路由组件就会变成 Test 组件
4、工作原理:底层原理依靠的是浏览器的 history(BOM 对象上面),专门用来管理路由的,原本底层的操作 API 过于繁琐,所以一般都使用三方封装好的。
history 模式,直接使用 H5 推出的 API,个别旧版本的浏览器可能不支持
hash 值(锚点)跳转不会刷新页面,也会留下历史记录。兼容性好
3、react-router-dom(Web 开发使用) 的理解
1、React 的一个插件库
2、专门用来实现一个 SPA 应用
3、基于 React 的项目基本都会用到此库
4、react-router 有三种:Web(适用 web 开发)、native(适用 React Native 开发)、any(适用任何场景)
5、路由器(router)是用来管理 路由(route) 的
二、react-router-dom 相关 API
npm i react-router-dom@5
1、内置组件
1.1 BrowserRouter
下面代码中相当于有两个 路由器包裹,所以没法统一管理
jsx
<div className="row">
<div className="sidebar">
<BrowserRouter>
<Link className="list-item active" to="/about">
About
</Link>
<Link className="list-item" to="/home">
Home
</Link>
</BrowserRouter>
</div>
<div className="panel">
<BrowserRouter>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</BrowserRouter>
</div>
</div>
1.2 HashRouter
路径都会有一个 # 号,后面的东西都不会作为资源发送给服务器端
1.3 Route
注册路由
1.4 Redirect
放在路由注册的最下方,如果所有的路由都没有匹配上,就跟着 redirect 走
1.5 Link
原生 html 中,使用 a 标签跳转不同的页面
在 React 中靠路由链接实现切换组件---编写路由链接
Link 的属性跟 a 标签一致
外层需要 包裹 或者
没法高亮菜单
1.6 NavLink
点击谁就会默认给谁追加一个 类样式名 .active
有一个 属性名 activeClassName='activeClass'
总结:
1、NavLink 可以实现路由链接的高亮,通过 activeClassName 指定样式名
2、组件标签,标签体内容是一个特殊的标签属性
3、通过 this.props.children 可以获取标签体内容
基本使用:
jsx
import React, { Component } from 'react'
import { NavLink, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Header from './components/Header'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<NavLink
activeClassName="activeClass"
className="list-item"
to="/about"
>
About
</NavLink>
<NavLink
activeClassName="activeClass"
className="list-item"
to="/home"
>
Home
</NavLink>
</div>
<div className="panel">
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</div>
</div>
</div>
)
}
}
二次封装使用:
MyNavLink 组件
jsx
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
let { to, pathName } = this.props
return (
<NavLink activeClassName="activeClass" className="list-item" to={to}>
{pathName}
</NavLink>
)
}
}
App 组件
jsx
import React, { Component } from 'react'
import { Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about" pathName="About"></MyNavLink>
<MyNavLink to="/home" pathName="Home"></MyNavLink>
{/* <NavLink activeClassName='activeClass' className="list-item" to='/about' >About</NavLink>
<NavLink activeClassName='activeClass' className="list-item" to='/home' >Home</NavLink> */}
</div>
<div className="panel">
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</div>
</div>
</div>
)
}
}
MyNavLink 组件的第二种封装(更加简洁好用)
jsx
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink
activeClassName="activeClass"
className="list-item"
{...this.props}
/>
)
}
}
App 组件
jsx
import React, { Component } from 'react'
import { Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
{/* <NavLink activeClassName='activeClass' className="list-item" to='/about' >About</NavLink>
<NavLink activeClassName='activeClass' className="list-item" to='/home' >Home</NavLink> */}
</div>
<div className="panel">
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</div>
</div>
</div>
)
}
}
1.7 Switch
注册的路由一个以上可以包裹起来,这时候匹配到第一个就直接停止了
总结:
1、通常情况下,path 和 component 是一一对应的关系
2、Switch 可以提高路由匹配效率(单一匹配)
jsx
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Test from './pages/Test'
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/test">Test</MyNavLink>
{/* <NavLink activeClassName='activeClass' className="list-item" to='/about' >About</NavLink>
<NavLink activeClassName='activeClass' className="list-item" to='/home' >Home</NavLink> */}
</div>
<div className="panel">
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
</Switch>
</div>
</div>
</div>
)
}
}
2、路由的基本使用
1、明确好界面中的 导航区、展示区
2、导航区的 a 标签改为 Link 标签
Demo 3、展示区写 Route 标签进行路径的匹配 4、的最外层包裹了一个 或者
3、路由组件和一般组件
总结区别:
1、写法不同:
一般组件:
路由组件: <Route path="/demo" component={Demo}
2、存放位置不同:
一般组件:components
路由组件:pages
3、接收到的 props 不同:
一般组件:与组件标签时传递了什么,就接收什么
路由组件:接收到三个固定的属性
javascript
{
"history": {
"action": "POP",
"location": {
"pathname": "/about",
"search": "",
"hash": "",
"key": "mlt4yu"
}
},
"location": {
"pathname": "/about",
"search": "",
"hash": "",
"key": "mlt4yu"
},
"match": {
"path": "/about",
"url": "/about",
"isExact": true,
"params": {}
}
}
3.1 路由组件
规范些的写法是 放到 pages 中
不用传参数也可以收到 props
jsx
<Route path="/about" component={About} />
3.2 一般组件
规范些的写法是 放到 components 中
没有传参就不会收到 props
jsx
<Home />
4、解决多级路由下页面刷新样式丢失问题
BrowserRouter 刷新页面样式丢失,是出现在二级(多级)路由下,引入样式时会将路由路径添加进去,这时候样式就会丢失,react 就会返回 index.html 的内容回来
./ 以当前文件出发,在当前文件夹下面去找
解决方法 1:(去掉 .)
表示直接去 localhost:3000/css/bootstrap.css 查找
解决方法 2:(%PUBLIC_URL%)
绝对路径解决
解决方法 3:将路由模式改为 HashRouter
注意:包管理 npm 和 yarn 不要混着用,否则容易造成包的丢失
5、路由的模糊匹配与精确匹配
5.1 匹配的路径需要的和顺序都对上,默认就是模糊匹配
/home/a/b 和/home 就是可以匹配的
jsx
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Test from './pages/Test'
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
<MyNavLink to="/test">Test</MyNavLink>
{/* <NavLink activeClassName='activeClass' className="list-item" to='/about' >About</NavLink>
<NavLink activeClassName='activeClass' className="list-item" to='/home' >Home</NavLink> */}
</div>
<div className="panel">
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
</Switch>
</div>
</div>
</div>
)
}
}
5.2 精确匹配 使用 exact 属性
jsx
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Test from './pages/Test'
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
<MyNavLink to="/test">Test</MyNavLink>
{/* <NavLink activeClassName='activeClass' className="list-item" to='/about' >About</NavLink>
<NavLink activeClassName='activeClass' className="list-item" to='/home' >Home</NavLink> */}
</div>
<div className="panel">
<Switch>
<Route path="/about" exact component={About} />
<Route path="/home" exact component={Home} />
<Route path="/home" exact component={Test} />
</Switch>
</div>
</div>
</div>
)
}
}
5.3 如果模糊匹配引发异常,才开启 精确匹配,一般不会全部开启精确(严格)匹配
5.4 总结
1、默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序一致)
2、开启严格匹配
3、严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
6、Redirect 的使用
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由
jsx
import React, { Component } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Test from './pages/Test'
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './App.css'
// 创建并暴露 App 组件
export default class App extends Component {
render() {
return (
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
<MyNavLink to="/test">Test</MyNavLink>
{/* <NavLink activeClassName='activeClass' className="list-item" to='/about' >About</NavLink>
<NavLink activeClassName='activeClass' className="list-item" to='/home' >Home</NavLink> */}
</div>
<div className="panel">
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
<Redirect to="/about" />
</Switch>
</div>
</div>
</div>
)
}
}
7、二级路由(嵌套路由)
React 中路由注册从 App 里面开始的,路由的匹配都是优先注册的先匹配
总结:
1、注册子路由时要写上父路由的 path 值
2、路由的匹配时按照注册路由顺序进行匹配
jsx
import React, { Component } from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import MyNavLink from '../../components/MyNavLink'
import News from './News'
import Message from './Message'
export default class Home extends Component {
render() {
return (
<div>
<h2>我是 Home 的页面内容</h2>
<div className="sidebar2">
<MyNavLink to="/home/news">news</MyNavLink>
<MyNavLink to="/home/message">Message</MyNavLink>
</div>
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
<Redirect to="/home/news" />
</Switch>
</div>
)
}
}
8、向路由组件传递参数
8.1 params 参数
路由链接(携带参数):
jsx
<Link to={`/home/message/detail/${el.id}`}>{el.title}</Link>
注册路由(声明接收):
jsx
<Route path="/home/message/detail/:id" component={Detail} />
接收参数
jsx
let { id } = this.props.match.params
8.2 search 参数
不需要声明接收, 正常注册路由即可
路由链接(携带参数):
jsx
<Link to={`/home/message/detail?id=${el.id}`}>{el.title}</Link>
注册路由(无需声明,正常注册即可):
jsx
<Route path="/home/message/detail" component={Detail} />
接收参数
jsx
this.props.location.search
备注:获取到的 search 时 urlencoded 编码字符串,需要借助 querystring 解析
8.3 state 参数(路由组件独有的 state)
BrowserRouter 一直维护 history,所以刷新页面 state 中的数据不会丢失。如果清空浏览器缓存和历史记录等才会没有 state。
路由链接(携带参数):
jsx
<Link
to={{
pathname: '/home/message/detail',
state: {
id: el.id,
},
}}
>
{el.title}
</Link>
注册路由(无需声明,正常注册即可):
jsx
<Route path="/home/message/detail" component={Detail} />
接收参数
jsx
this.props.location.state
备注:参数不体现在地址栏上面,但刷新也可以保留住参数
9、push 和 replace
默认时 push 模式,不做替换,能够留下所有的痕迹
开启 replace 模式:不留下痕迹
10、编程式路由导航
两种模式(push 和 replace)可以携带三种形式的参数,携带参数和路由注册以及接收参数都需要保持一致
借助 this.props.history 对象上的 API 进行前进、后退
jsx
this.props.history.replace
this.props.history.push
this.props.history.push(path, state) // state 参数传递
this.props.history.goForward() //前进
this.props.history.goBack() // 后退
this.props.history.go() // 整数为前进 n 步,负数为后退 n 步,0为当前页刷新
11、withRouter 的使用
一般组件没有 history,也就是一般组件中不能用路由导航的 API
withRouter 能够接收一个一般组件,将一般组件加工后,能够拥有路由组件的 API
12、BrowserRouter 与 HashRouter 的区别
1、底层原理不一样
BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本
HashRouter 使用的 URL 的哈希值
2、path 表现形式不一样
BrowserRouter 的路径中没有 # ,例如: localhost:3000/demo/test
HashRouter 的路径中有 # ,例如: localhost:3000/#/demo/test
3、刷新后对路由 state 参数的影响
BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
HashRouter 刷新后会导致路由 state 参数的丢失,因为它没有 history 对象
4、备注:HashRouter 可以用于解决一些路径错误相关的问题