一、SPA的理解
1.单页Web应用(single page web application, SPA)
2.整个应用只有一个完整的页面
3.点击页面中的链接不会刷新页面,只会做页面的局部刷新
4.数据都需要通过ajax请求获取,并在前端异步展现
二、路由的理解
1.什么是路由
(1)一个路由就是一个映射关系(key:value)
(2)key为路径,value可能是func或者是component
2.路由分类
(1)后端路由:
1>理解:value是function,用来处理客户端提交的请求
2>注册路由:router.get(path,function(req,res))
3>工作过程:当node接受到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回相应数据。
(2)前端路由:
1>浏览器端路由,value是component,用于展示页面内容
2> 注册路由:<Route path="/test" component={Test}></Route>
3>工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件
前端路由的工作原理:
History两种工作模式:BrowserHistory,HashHistory
History.createBrowserHistory()直接使用h5推出的history身上的API
History.createHashHistory() 使用hash值(锚点),不会引起页面的刷新,但是可以留下历史记录
3.react-router的理解(web,native,any)
react-router-dom是专门为web开发人员设计的
(1)react的一个插件库
(2)专门用来实现一个SPA应用
(3)基于react的项目基本都会用到此库
4.react-router相关API
内置组件:
(1)<BrowserRouter>
(2)<HashRouter>
(3)<Route>
(4) <Redirect>
(5)<Link>
(6)<NavLink>
(7)<Switch>
其他:
(1)history对象
(2)match对象
(3)withRouter对象
三、路由的使用
1.路由的基本使用
(1)明确好界面中的导航区,展示区
(2)导航区的标签使用Link标签
(3)展示区写Route标签进行路径的匹配
<Route path="/xxxx" component={demo}/>
(4)<App>的最外层包裹了一个<BrowserRouter>或<HashRouter>
代码示例:
//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>
<h2>React Router Demo</h2>
{/* 在React中靠路由实现切换组件
<Router>分为BrowserRouter,HashBrowser
*/}
{/* 编写路由链接 */}
<Link to="/about">About</Link>
<Link to="/home">home</Link>
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
)
}
}
// index.js
// 引入React
import React from 'react';
// 引入ReactDOM
import ReactDOM from 'react-dom/client';
import './index.css';
import {BrowserRouter} from 'react-router-dom'
// 引入App组件
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// About.jsx
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<div>
<h3>我是About的内容</h3>
</div>
)
}
}
// Home.jsx
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<div>
<h3>我是Home的内容</h3>
</div>
)
}
}
文件目录
2.路由组件与一般组件的区别
(1)写法不同
一般:<Demo/>
路由组件: <Route path="/xxxx" component={demo}/>
(2)存放位置不同
一般组件:components
路由组件:pages
(3)接收到的props不同
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性(history、location、match)
3.NavLink与封装NavLink
(1)NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
(2)标签体内容是一个特殊的标签属性
(3)通过this.props.children可以获取标签体内容
添加高亮
<NavLink activeClassName="demo" to="/home">home</NavLink>
封装
// components/MyNavLink
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink activeClassName="demo" {...this.props} />
)
}
}
使用
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
4.Switch的使用
(1)通常情况下,path和component是一一对应的关系
(2)Switch可以提高路由匹配效率(单一匹配)
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="/home" component={Test}/>
</Switch>
5.解决多级路径刷新页面样式丢失的问题
(1)public/index.html中引入样式不写./写/(常用)
(2)public/index.html中样式时不写./写%PUBLIC_URL%(常用)
(3)使用HashRouter
6.路由的模糊匹配与严格匹配
(1)默认使用的是模糊匹配(输入的路径必须包含要匹配的路径,且顺序要一致)
(2)开启严格匹配
(3)严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
<Switch>
<Route exact path="/about" component={About}/>
<Route exact path="/home" component={Home}/>
</Switch>
7.Redirect的使用
(1)一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
(2)具体编码:
<Route exact path="/about" component={About}/>
<Route exact path="/home" component={Home}/>
<Redirect to="/about"/>
8.嵌套路由的使用
(1)注册子路由时要写上父路由的path值
(2)路由的匹配时按照注册路由的顺序进行的
// MyNavLink 这个是上面封装的NavLink公共组件,Message和News组件是当做路由组件建立的,里边的内容可以自己定义
<MyNavLink to="/home/message">Message</MyNavLink>
<MyNavLink to="/home/news">News</MyNavLink>
{/* 注册路由 */}
<Switch>
<Route path="/home/message" component={Message}></Route>
<Route path="/home/news" component={News}></Route>
<Redirect to="/home/news"></Redirect>
</Switch>
9.向路由组件中传递params参数
(1)路由链接(携带参数):<Link to={'/home/message/detail/18'}>详情</Link>
(2)注册路由(声明接收):<Route path="/home/message/detail/:id/:title" component={Detail}></Route>
(3)接收参数:const {id, title} = this.props.match.params
文件目录情况:说明:Message是第二层路由,Detail是要展示的具体内容
代码示例:
// Message.jsx
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
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}></Route>
</div>
)
}
}
// Detail.jsx
import React, { Component } from 'react'
const DetailData = [
{id: '01', content: 'xxxx'},
{id: '02', content: 'zxxx'},
{id: '03', content: 'vxxx'},
]
export default class Detail extends Component {
render() {
// 接收params参数
const {id, title} = this.props.match.params
const finResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
<div>
<ul>
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content: {finResult.content}</li>
</ul>
</div>
)
}
}
10.向路由组件中传递search参数
(1)路由链接(携带参数):<Link to={'/home/message/detail/?id=01&title=11'}>详情</Link>
(2)注册路由(无需声明,正常注册路由即可):<Route path="/home/message/detail" component={Detail}></Route>
(3)接收参数:const {search} = this.props.location
备注:获取到的search是urlencode编码,需要借助querystring解析,目前我使用的版本需要querystring-es3
// Message.jsx
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
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> */}
{/* 向路由组件传递search参数 */}
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr/>
{/* 声明接收params参数 */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */}
{/* 声明接收search参数(无需声明接收,正常注册路由即可) */}
<Route path="/home/message/detail" component={Detail}></Route>
</div>
)
}
}
// Detail.jsx
import React, { Component } from 'react'
import qs from 'querystring-es3'
const DetailData = [
{id: '01', content: 'xxxx'},
{id: '02', content: 'zxxx'},
{id: '03', content: 'vxxx'},
]
export default class Detail extends Component {
render() {
// 接收params参数
// const {id, title} = this.props.match.params
// 接收search参数 ?id =01&title='111' 需要做转换 可以使用querystring库
const {search} = this.props.location
const {id, title} = qs.parse(search.slice(1))
const finResult = DetailData.find((detailObj) => {
return detailObj.id === id
})
return (
<div>
<ul>
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content: {finResult.content}</li>
</ul>
</div>
)
}
}
11.向路由组件中传递state参数
(1)路由链接(携带参数):<Link to={{pathname: '/home/message/detail', state:{id:msgObj.id,title:msgObj.title}}>详情</Link>
(2)注册路由(无需声明,正常注册路由即可):<Route path="/home/message/detail" component={Detail}></Route>
(3)接收参数:const {id,title} = this.props.location.state
备注:刷新也可以保留住参数
代码示例:
// Message.jsx
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
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> */}
{/* 向路由组件传递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}></Route> */}
{/* 声明接收search参数(无需声明接收,正常注册路由即可) */}
{/* <Route path="/home/message/detail" component={Detail}></Route> */}
{/* state参数(无需声明接收,正常注册路由即可) */}
<Route path="/home/message/detail" component={Detail}></Route>
</div>
)
}
}
// Detail.jsx
import React, { Component } from 'react'
// import qs from 'querystring-es3'
const DetailData = [
{id: '01', content: 'xxxx'},
{id: '02', content: 'zxxx'},
{id: '03', content: 'vxxx'},
]
export default class Detail extends Component {
render() {
// 接收params参数
// const {id, title} = this.props.match.params
// 接收search参数 ?id =01&title='111' 需要做转换 可以使用querystring库
// const {search} = this.props.location
// const {id, title} = qs.parse(search.slice(1))
// 接收state参数
const {id, title} = this.props.location.state || {}
const finResult = DetailData.find((detailObj) => {
return detailObj.id === id
}) || {}
return (
<div>
<ul>
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content: {finResult.content}</li>
</ul>
</div>
)
}
}
12.编程式路由导航
借助this.props.history对象上的API对操作路由跳转,前进,后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
代码示例:
//message.jsx
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
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.replace(`/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}></Route> */}
{/* 声明接收search参数(无需声明接收,正常注册路由即可) */}
{/* <Route path="/home/message/detail" component={Detail}></Route> */}
{/* state参数(无需声明接收,正常注册路由即可) */}
<Route path="/home/message/detail" component={Detail}></Route>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
// Detail.jsx
import React, { Component } from 'react'
// import qs from 'querystring-es3'
const DetailData = [
{id: '01', content: 'xxxx'},
{id: '02', content: 'zxxx'},
{id: '03', content: 'vxxx'},
]
export default class Detail extends Component {
render() {
// 接收params参数
// const {id, title} = this.props.match.params
// 接收search参数 ?id =01&title='111' 需要做转换 可以使用querystring库
// const {search} = this.props.location
// const {id, title} = qs.parse(search.slice(1))
// 接收state参数
const {id, title} = this.props.location.state || {}
const finResult = DetailData.find((detailObj) => {
return detailObj.id === id
}) || {}
return (
<div>
<ul>
{/* 路由跳转的push与replace */}
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content: {finResult.content}</li>
</ul>
</div>
)
}
}
13.withRouter的使用
withRouter可以加工一般组件,让一般组件具备路由所持有的API
withRouter的返回值是一个新组件
14.BrowserRouter与HashRouter的区别
(1)底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值
(2)url表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
(3)刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中
HashRouter刷新后会导致路由state参数的丢失
(4)备注:HashRouter可以用于解决一些路径错误相关的问题