1. 相关理解
1.1. SPA的理解
- 单页Web应用(single page web application,SPA)。
- 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过ajax请求获取, 并在前端异步展现。
1.2. 路由的理解
-
什么是路由?
- 一个路由就是一个映射关系(key:value)
- key为路径, value可能是function或component
-
路由分类
-
后端路由:
- 理解: value是function, 用来处理客户端提交的请求。
- 注册路由: router.get(path, function(req, res))
- 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
-
前端路由:
- 浏览器端路由,value是component,用于展示页面内容。
- 注册路由:
- 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
-
1.3. react-router-dom的理解
- react的一个插件库。
- 专门用来实现一个SPA应用。
- 基于react的项目基本都会用到此库。
1.4 react 如何实现SPA?
单页面 多组件 借助history
BOM上就有History,但是一般不直接使用BOM上的History(操作繁琐),而是引用一个history的库来操作history,不过结果也是靠这个history去操作BOM上的history来实现,主要是为了操作简单
-
流程
- 点击导航区
- 游览器的路径被修改(游览器可以监测路径是否被修改history.listen(location=>{})
- 展示对应组件
-
方法1:直接调用H5推出的history身上的API
let history=History.createBrowserHistory()
xxxxxx/home
-
方法2:hash值(锚点#)兼容性较好
let history=History.createHashHistory()
xxxxxx/#/home —锚点也是不会导致页面刷新的
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 --save react-router-dom
- 引入bootstrap.css:
3.2. 代码
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.render(
//用<BrowserRouter>包裹
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
// ReactDOM.render(<App/>,document.getElementById('root'))
App.js
import React,{Component} from 'react'
import {Link,Routes,Route} from 'react-router-dom'
import About from './component/About'
import Home from './component/Home'
// 创建并暴露组件
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 active" 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">
{/*注册路由 ----v6需用<Routes>包裹*/}
<Routes>
<Route path="/about" element={<About/>}/>
<Route path="/home" element={<Home/>}/>
</Routes>
</div>
</div>
</div>
</div>
</div>
)
}
}
// 暴露App组件
// export default App
components—About
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>
我是About
</h3>
)
}
}
components—Home
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<h3>我是Home</h3>
)
}
}
3.3.注意点
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
- 原生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 to="/xxxxx">Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path='/about' component={About}/>
<Route path='/home' component={Home}/>
//v6:
<Routes>
<Route path="/about" element={<About/>}/>
<Route path="/home" element={<Home/>}/>
</Routes>
4.的最外侧包裹了一个或
- 包裹和的区别??
-
包裹采用多级路由时,
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
- 加.会导致多层级路由时寻找bootstrap样式时路径错误
- public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
-
包裹采用多级路由时
可以写./
-
4. 基本路由的扩展
4.1.NavLink的使用
App.js
import React,{Component} from 'react'
import {Routes,Route,Navigate} from 'react-router-dom'
import MyNavLink from './components/MyNavLink' //封装MavLink组件
import About from './pages/About'
import Home from './pages/Home'
import Header from './components/Header'
// 创建并暴露组件
export default class App extends Component{
render(){
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/**NavLink 表示点了谁就给谁加一个active */}
{/* <NavLink className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")} to="/about">About</NavLink> *v6版本activeClassName不生效 */}
{/* <NavLink className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")} to="/home">Home</NavLink> */}
{/* 封装NavLink 注意:标签体内容属于特殊的标签属性,会在子组件的props.children中得到*/}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink> {/**模糊匹配:输入的路径必须包含要匹配的路径且顺序一致,添加exact可开启严格匹配 */}
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/*注册路由 */}
<Routes>
<Route path="/about" element={<About/>}/>
<Route path="/home" element={<Home/>}/>
{/* <Route exact path="/home" element={<Home/>}/> exact使用严格匹配*/}
{/* 解决路由重定向,使得在匹配不成功时用来兜底的 */}
{/* <Redirect to="/about"/> v6版本移除Redirect了*/}
<Route path="*" element={<Navigate to="/about" />} />
</Routes>
</div>
</div>
</div>
</div>
</div>
)
}
}
// 暴露App组件
// export default App
pages—About
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>
我是About
</h3>
)
}
}
pages—Home
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<h3>我是Home</h3>
)
}
}
components—Header
import React, { Component } from 'react'
export default class Header extends Component {
render() {
// console.log('Header组件收到的props是',this.props);
return (
<div className="page-header"><h2>React Router Demo</h2></div>
)
}
}
components—MyNavLink
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<div>
{/* ...实现批量传递,包括标签体About和Home */}
<NavLink className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")} {...this.props}/>
</div>
)
}
}
4.2 注意点
路由组件与一般组件
1.写法不同:
-
一般组件:
-
路由组件:<Route path="/about" element={/>
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"
- history:
NavLink与封装NavLink
- NavLink可以实现路由链接的高亮,通过activeClassName指定样式名(v6版本activeClassName不生效)
- v6可采用三元式
className={({ isActive }) => "list-group-item" + (isActive ? " highlight" : "")}
Switch的使用(已弃用)
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/> {/*不匹配*/}
</Switch>
解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写 ./ 写 / (常用)
- public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
- 使用HashRouter
<link rel="stylesheet" href="./css/bootstrap.css"> <!--加.会导致多层级路由时寻找bootstrap路径错误-->
<link rel="stylesheet" href="/css/bootstrap.css">
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css"> <!--这种方法也行 或者把broswerrouter改为hashrouter -->
路由的严格匹配与模糊匹配
-
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
-
开启严格匹配:
<Route exact={true} path="/about" component={About}/>//v6: <Route exact path="/home" element={<Home/>}/> -
严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect的使用(已弃用)
-
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
-
具体编码:
<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch> -
v6写法:
<Routes> <Route path="/about" element={<About/>}/> <Route path="/home" element={<Home/>}/> <Route path="*" element={<Navigate to="/about" />} /> </Routes>
5. 嵌套路由使用
5.1.效果
5.2.代码
App.js
import React,{Component} from 'react'
import {Routes,Route,Navigate} from 'react-router-dom'
import MyNavLink from './components/MyNavLink'
import About from './pages/About'
import Home from './pages/Home'
import Header from './components/Header'
// 创建并暴露组件
export default class App extends Component{
render(){
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/*注册路由 */}
<Routes>
<Route path="/about" element={<About/>}/>
<Route path="/home/*" element={<Home/>}/> {/**v6需加/*才能实现二级路由 */}
<Route path="*" element={<Navigate to="/about" />} />
</Routes>
</div>
</div>
</div>
</div>
</div>
)
}
}
// 暴露App组件
// export default App
pages—Home
import React, { Component } from 'react'
import {Routes,Route,Navigate} from 'react-router-dom'
import MyNavLink from '../../components/MyNavLink'
import News from './News'
import Messages from './Messages'
export default class Home extends Component {
render() {
return (
<div>
<h3>我是Home</h3>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="news">News</MyNavLink>
{/* <MyNavLink to="/home/news">News</MyNavLink> v6中不需要如此 */}
</li>
<li>
<MyNavLink to="message">Message</MyNavLink>
</li>
</ul>
<Routes>
<Route path="news" element={<News/>}/>
{/* <Route path="/home/news" component={News}/> v6中不需要如此,且上级路由path后缀需加/* */}
<Route path="message" element={<Messages/>}/>
<Route path="*" element={<Navigate to="news" />} />
</Routes>
</div>
</div>
)
}
}
pages—Home—News
import React, { Component } from 'react'
export default class News extends Component {
render() {
return (
<div>
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
</div>
)
}
}
pages—Home—Messages
import React, { Component } from 'react'
export default class Messages extends Component {
render() {
return (
<div>
<ul>
<li>
<a href="/message1">message001</a>
</li>
<li>
<a href="/message2">message002</a>
</li>
<li>
<a href="/message/3">message003</a>
</li>
</ul>
</div>
)
}
}
5.3.注意点
v5:
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
v6:
1.父路由需加/*才能实现二级路由
2.子路由不需要写父路由的path值
3.路由的匹配是按照注册路由的顺序进行的
6. 向路由组件传递参数数据
6.1.效果
6.2.向路由组件传递params参数
pages—Home—Messages
import React, { Component } from 'react'
import Detail from './Detail'
import {Link,Routes,Route} from 'react-router-dom'
export default class Messages 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 (
<div>
<li key={msgObj.id}>
{/* 向路由组件传递params参数
<Link to={`detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递search参数 */}
{/* <Link to={`detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递state参数 */}
{/* <Link to={{pathname:'detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link> */}
<Link to='/home/message/detail' state={{ id: msgObj.id, title: msgObj.title }}>{msgObj.title}</Link>
</li>
</div>
)
})
}
</ul>
<hr/>
<Routes>
{/* 声明接收params参数
<Route path="detail/:id/:title" element={<Detail/>}/> */}
{/*search参数无需声明接收 */}
{/* <Route path="detail" element={<Detail/>}/> */}
{/*state参数无需声明接收 */}
<Route path="detail" element={<Detail/>}/>
</Routes>
</div>
)
}
}
pages—Home—Messages—Detail(params使用函数式组件写法)
// import React, { Component } from 'react'
import {useParams} from 'react-router-dom'
const DetailData=[
{id:'01',content:'消息1的内容'},
{id:'02',content:'消息2的内容'},
{id:'03',content:'消息3的内容'}
]
// v6版本需要使用function
export default function Detail(){
let params=useParams()
const findcontent=DetailData.find((detailObj)=>{
return detailObj.id===params.id
})||{}
return (
<ul>
<li>ID:{params.id}</li>
<li>TITLE:{params.title}</li>
<li>CONTENT:{findcontent.content}</li>
</ul>
)
}
pages—Home—Messages—Detail(search和state使用函数式组件写法)
import React, { Component } from 'react';
import { useSearchParams } from 'react-router-dom';
// v6使用class组件。需要封装一下。利用hoc组件来获取参数,然后传递给class组件
//search写法
/*
function myWithRouter(Detail) {
return (props) => {
// let [searchParams, setSearchParams] = useSearchParams();
let [searchParams] = useSearchParams();
const params = {
id: searchParams.get('id'),
title: searchParams.get('title')
}
return <Detail {...props} params={params} />
}
}
*/
//state写法
function myWithRouter(Detail) {
return (props) => {
let location = useLocation();
/*
这里写成 location.state || {}
因为state在刷新后可能为空,所以这里为空时设置为空对象
*/
const stateObj = location.state || {};
const params = {
id: stateObj.id,
title: stateObj.title
}
return <Detail {...props} params={params} />
}
}
//以下通用
const DetailData = [
{ id: '01', content: '11111111111111111111' },
{ id: '02', content: '22222222222222222222' },
{ id: '03', content: '33333333333333333333' }
]
class Detail extends Component {
render() {
const contentStr = DetailData.find((detailObj) => {
return detailObj.id === this.props.params.id;
})
return (
<ul>
<li>ID:{this.props.params.id}</li>
<li>TITLE:{this.props.params.title}</li>
<li>CONTENT:{contentStr.content}</li>
</ul>
)
}
}
export default myWithRouter(Detail);
6.3.注意点
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
- v6:
-
路由不需要写父路由路径
-
useParams接收参数
let params=useParams() -
不使用类式组件,需使用函数式组件
-
或者采用高阶组件方式,定义一个函数组件包裹类式组件
-
search参数
- 路由链接(携带参数):
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
- 注册路由(无需声明,正常注册即可):
<Route path="/home/message/detail" component={Detail}/>
-
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))
-
v6:
- useSearchParams
let [searchParams] = useSearchParams(); const params = { id: searchParams.get('id'), title: searchParams.get('title') }
state参数
- 路由链接(携带参数):
<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 || {}
-
v6:
- 路由链接(携带参数):
<Link to='/home/message/detail' state={{ id: msgObj.id, title: msgObj.title }}>{msgObj.title}</Link>-
useLocation
备注:刷新也可以保留住参数
let location = useLocation(); /* 这里写成 location.state || {} 因为state在刷新后可能为空,所以这里为空时设置为空对象 */ const stateObj = location.state || {}; const params = { id: stateObj.id, title: stateObj.title }
7. 多种路由跳转方式
7.1效果
7.2 代码
pages—Home—Messages
import React from 'react'
import Detail from './Detail'
import {Link,Routes,Route,useNavigate} from 'react-router-dom'
//v6版本使用编程式路由时
//1.需把类式组件改为函数式组件,或者在类组件外面包裹一层函数组件
//2.this.props.history.push无法使用,需使用useNavigate
export default function Messages() {
let Navigate = useNavigate();
let state={
messageArr:[
{id:'01',title:'消息1'},
{id:'02',title:'消息2'},
{id:'03',title:'消息3'}
]
}
function showPush(id,title){
// this.props.history.push(`/home/message/detail/${id}/${title}`)
Navigate(`/home/message/detail/${id}/${title}`,{replace:false})
}
function showReplace(id,title){
// this.props.history.replace(`/home/message/detail/${id}/${title}`)
Navigate(`/home/message/detail/${id}/${title}`,{replace:true})
}
function back(){
Navigate(-1)
}
function forward(){
Navigate(1)
}
function go(){
Navigate(2)
}
// render() {
const{messageArr}=state
return (
<div>
<ul>
{
messageArr.map((msgObj)=>{
return (
<div key={msgObj.id}>
<li>
{/* 向路由组件传递params参数 */}
<Link to={`detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<button onClick={()=>showPush(msgObj.id,msgObj.title)}>push跳转</button>
<button onClick={()=>showReplace(msgObj.id,msgObj.title)}>replace跳转</button>
</li>
</div>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
<Routes>
<Route path="detail/:id/:title" element={<Detail/>}/>
</Routes>
<button onClick={()=>back()}>回退</button>
<button onClick={()=>forward()}>前进</button>
<button onClick={()=>go()}>走2</button>
</div>
)
// }
}
7.3.注意点
push会留下历史痕迹,replace直接替换
-
借助this.props.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
-
v6:
1.需把类式组件改为函数式组件,或者在类组件外面包裹一层函数组件
2.this.props.history.push无法使用,需使用useNavigate
8.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可以用于解决一些路径错误相关的问题。