目录
3.6 如何在一般组件中使用路由组件的API?(WithRouter)
3.7 BrowserRouter与HashRouter的区别
一、什么是SPA?
- 单页面应用
- 整个应用只有一个完整的页面
- 点击页面中的链接不会刷新整个页面,只会做页面的局部更新
- 数据都需要通过ajax请求获取, 并在前端异步展现
二、什么是路由?
一个路由就是一个映射关系(key:value)
key为路径, value可能是function或component
-
后端路由
- 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
- 注册路由: router.get(path, function(req, res))
- 理解: value是function, 用来处理客户端提交的请求。
app.get("/search/users", function (req, res) {
const {q} = req.query
axios({
url: 'https://api.github.com/search/users',
params: {q}
}).then(response => {
res.json(response.data)
})
})
-
前端路由
- 浏览器端路由,value是component,用于展示页面内容。
- 注册路由: <Route path="/test" component={Test}>
- 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
- 前端路由借助BOM上的history
补充:
1. 浏览器的历史记录是一个栈的结构
栈和队列就是常见的受限的线性结构。其限制是仅允许在表的一端进行插入和删除操作。相当于堆积木,最下方元素是栈底,最上方是栈顶。
向一个栈插入新元素又称为进栈、入栈或压栈,它是把新元素放到栈顶元素的上方,使之成为栈顶。从一个栈删除元素又称作出栈或退栈,它把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>前端路由的基石_history</title> </head> <body> <a href="http://www.atguigu.com" onclick="return push('/test1') ">push test1</a><br><br> <button onClick="push('/test2')">push test2</button><br><br> <button onClick="replace('/test3')">replace test3</button><br><br> <button onClick="back()"><= 回退</button> <button onClick="forword()">前进 =></button> <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 } function replace (path) { history.replace(path) } function back() { history.goBack() } function forword() { history.goForward() } history.listen((location) => { console.log('请求路由路径变化了', location) }) </script> </body> </html>
每次push,都往上堆,最上方形成栈顶,history.html为栈底。replace则为替代,也就是替代当前栈顶为 /test3
锚点跳转不会引起页面的刷新,锚点跳转可以留下历史记录。
2. BOM与DOM
javascript 由三部分组成,ECMAScript和Web API(DOM和BOM)
1. DOM:文档对象模型,用于处理HTML和XML文档内容的标准编程接口API,一般用于修改页面内容
- 页面由有层次的树状结构节点构成,简称 DOM 树。
- 浏览器根据 HTML 标签生成的 JS 对象,简称 DOM 对象。JS通过document对象 来对HTML/XML文档进行操作。
2. BOM:浏览器对象模型,用于操作浏览器而出现的API,一般用于调整浏览器窗口大小、标签页跳转
- BOM对象最根本的是window,是JS中的全局对象
- window 对象下包含了 navigator、location、document、history、screen 5个属性
以及
- setTimeout、setInterval
- alert()
- parseInt()
三、react-router-dom
3.1 基本使用
- 下载库
npm i react-router-dom@5
- 引入bootstrap
<link
rel="stylesheet"
href="%PUBLIC_URL%/css/bootstrap.css"
/>
- App.js
import "./App.css"
import { Link, Route } from "react-router-dom"
import About from "./Component/About"
import Home from "./Component/Home"
function App() {
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">
{/* <a></a>跳页面 */}
{/* <a
className="list-group-item active"
href="./about.html"
>
About
</a>
<a
className="list-group-item"
href="./home.html"
>
Home
</a> */}
{/* 路由link切组件,to一般小写*/}
<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注册路由 */}
<Route
path="/about"
component={About}
/>
<Route
path="/home"
component={Home}
/>
</div>
</div>
</div>
</div>
</div>
)
}
export default App
- About组件
import React, { Component } from "react"
export default class About extends Component {
render() {
return <h3>我是About的内容</h3>
}
}
常见错误
1. Invariant failed: You should not use <Link> outside a <Router>
最外层必须用 BrowserRouter 或 HashRouter 标签包裹起来
一般在index.js里设置
3.1.1 一般组件与路由组件
一般组件存放在componnets,props信息需要自行编辑
<Header a={1} />
路由组件存放在pages,会收到路由器给传递的三个最重要的props信息
<Route path="/about" component={About} />
3.1.2 NavLink与封装NavLink
NavLink可以实现路由链接的高亮,通过 activeClassName 指定样式名
标签体内容是个特殊的标签属性 children ,this.props.children可以获取标签图内容
- index.html
.demo {
background-color: rgb(45, 146, 45) !important;
color: white !important;
}
- 封装NavLink
import React, { Component } from "react"
import { NavLink } from "react-router-dom"
export default class MyNavLink extends Component {
render() {
//标签属性,标签体内容 children
console.log(this.props)
return (
<NavLink
activeClassName="demo"
className="list-group-item"
{...this.props}
/>
)
}
}
- App.js
{/* MyNavLink标签属性,to,a */}
<MyNavLink
to="/about"
a={1}
>
{/* 标签体内容,也是特殊的标签属性 children */}
About
</MyNavLink>
{/* <NavLink
to="/home"
children="Home"
/> */}
<MyNavLink to="/home">Home</MyNavLink>
3.1.3 Switch组件
注册多个路由时,用switch组件包裹,匹配上之后不会继续匹配
通常情况下,path和component是一一对应,Switch可以提高匹配效率(单一匹配)
<Switch>
<Route
path="/about"
component={About}
/>
<Route
path="/home"
component={Home}
/>
<Route
path="/home"
component={Test}
/>
</Switch>
3.1.4 可能出现样式丢失问题
<MyNavLink
to="/news/about"
a={1}
>
About
</MyNavLink>
<MyNavLink to="/news/home">Home</MyNavLink>
<Switch>
<Route
path="/news/about"
component={About}
/>
<Route
path="/news/home"
component={Home}
/>
</Switch>
解决方案一
index.html
写入样式时 / , 不写 ./
<link
rel="stylesheet"
href="/css/bootstrap.css"
/>
或 %PUBLIC_URL%
<link
rel="stylesheet"
href="%PUBLIC_URL%/css/bootstrap.css"
/>
解决方案二
index.js, 使用 HashRouter
import { HashRouter } from "react-router-dom"
3.1.5 路由的模糊匹配与精准匹配
默认模糊匹配,to,path从左开始匹配,path一定要有to中按顺序的路径
exact={true}精准匹配
<MyNavLink
to="/about"
a={1}
>
About
</MyNavLink>
<MyNavLink to="/home/a/bc">Home</MyNavLink>
<Switch>
<Route
exact={true}
path="/about"
component={About}
/>
<Route
path="/home/a"
component={Home}
/>
</Switch>
3.1.6 Redirect重定向
一般写在注册路由的最下方,当所有路由无法匹配时,跳转到指定路由
<MyNavLink
to="/about"
a={1}
>
About
</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
<Switch>
<Route
exact={true}
path="/about"
component={About}
/>
<Route
path="/home"
component={Home}
/>
{/* 重定向 */}
<Redirect to="/home" />
</Switch>
3.2 嵌套路由
路由的注册的是有顺序的,App.js是最先开始注册的。注册子路由时要写上父路由的path,路由的匹配是按照注册路由的顺序开始的。
- Home/News组件
import React, { Component } from "react"
export default class News extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
)
}
}
- Home/Messages组件
import React, { Component } from "react"
export default class Message extends Component {
render() {
return (
<ul>
<li>
<a href="/message1">message001</a>
</li>
<li>
<a href="/message2">message002</a>
</li>
<li>
<a href="/message/3">message003</a>
</li>
</ul>
)
}
}
- Home组件
import React, { Component } from "react"
import MyNavLink from "../../Component/MyNavLink"
import { Switch, Route, Redirect } from "react-router-dom"
import News from "./News"
import Message from "./Message"
export default class Home extends Component {
render() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news">News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message">Message</MyNavLink>
</li>
</ul>
<div>
<Switch>
<Route
path="/home/news"
component={News}
/>
<Route
path="/home/message"
component={Message}
/>
<Redirect to="/home/news" />
</Switch>
</div>
</div>
</div>
)
}
}
3.3 向路由参数传递参数
第一种 传递params参数
- Home/Message组件
import React, { Component } from "react"
import { Link, Route } from "react-router-dom"
import Detail from "./Detail"
export default class Message extends Component {
state = {
Msg: [
{
id: "01",
title: "message1",
content: "消息1",
},
{
id: "02",
title: "message2",
content: "消息2",
},
{
id: "03",
title: "message3",
content: "消息3",
},
],
}
render() {
const { Msg } = this.state
return (
<div>
<ul>
{Msg.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>
)
}
}
- Home/Message/Detail组件
import React, { Component } from "react"
const data = [
{
id: "01",
content: "消息详情1",
},
{
id: "02",
content: "消息详情2",
},
{
id: "03",
content: "消息详情3",
},
]
export default class Detail extends Component {
render() {
//console.log(this.props)
//接收params参数
const { id, title } = this.props.match.params
const findById = data.find((detailObj) => {
return detailObj.id === id
})
return (
<ul>
<li>ID:{id}</li>
<li>Title:{title}</li>
<li>Content:{findById.content}</li>
</ul>
)
}
}
要点:
路由链接携带参数:
<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
第二种 传递search参数
import qs from "qs"
let obj = { name: "Tom", age: 18 }
console.log(qs.stringify(obj)) //name=Tom&age=18
let str = "name=Mike&age=16"
console.log(qs.parse(str))//{name: "Mike", age: "16"}
- 向路由组件传递search参数
<Link
to={`/home/message/detail/?id=${msgobj.id}&title=${msgobj.title}`}
>
{msgobj.title}
</Link>
- search无需声明接收,正常接收即可
<Route
path="/home/message/detail"
component={Detail}
/>
- 接收search参数,是urllencoded编码字符串,需要借助qs解析
//key=value&key=value :urlencoded编码
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
第三种 传递state参数
- 向路由组件传递state参数
<Link
to={{
pathname: "/home/message/detail",
state: { id: msgobj.id, title: msgobj.title },
}}
>
{msgobj.title}
</Link>
- state无需声明接收,正常接收即可
<Route
path="/home/message/detail"
component={Detail}
/>
- 接收state参数
const { id, title } = this.props.location.state
报错内容
1. Uncaught TypeError: Cannot destructure property 'id' of 'this.props.location.state' as it is undefined.
const { id, title } = this.props.location.state || {}
2.Uncaught TypeError: Cannot read property 'content' of undefined
const findById = data.find((detailObj) => { return detailObj.id === id }) || {}
总结
1. location中state默认是undefined
2. state传递参数路径无提示
3. 三种传递参数方式,刷新参数都不会丢失
3.4 路由跳转的两种模式(push与replace)
默认路由跳转是push形式,会留下痕迹,进行压栈操作
replace是替换,替换栈顶,不留下痕迹
<Link
replace={true}
to={{
pathname: "/home/message/detail",
state: { id: msgobj.id, title: msgobj.title },
}}
>
{msgobj.title}
</Link>
3.5 编程式路由
只有路由组件才有history,主要就是history上的API,push、replace、goBack、goForWard、go等
import React, { Component } from "react"
import { Link, Route } from "react-router-dom"
import Detail from "./Detail"
export default class Message extends Component {
state = {
Msg: [
{
id: "01",
title: "message1",
content: "消息1",
},
{
id: "02",
title: "message2",
content: "消息2",
},
{
id: "03",
title: "message3",
content: "消息3",
},
],
}
//push跳转
pushShow = (id, title) => {
//编程式路由跳转
//this.props.history.push(`/home/message/detail/${id}/${title}`)
//this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)
this.props.history.push(`/home/message/detail`, { id, title })
}
//replace跳转
replaceShow = (id, title) => {
//replace跳转
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)
this.props.history.replace(`/home/message/detail`, { id, title })
}
//回退
backShow = () => {
this.props.history.goBack()
}
//前进
forwardShow = () => {
this.props.history.goForward()
}
//go
goShow = () => {
//2:前进2步;-2:回退2步
this.props.history.go(-2)
}
render() {
const { Msg } = this.state
return (
<div>
<ul>
{Msg.map((msgobj) => {
return (
<li key={msgobj.id}>
{/* 1.向路由组件传递params参数 */}
{/* <Link to={`/home/message/detail/${msgobj.id}/${msgobj.title}`}>
{msgobj.title}
</Link> */}
{/* 2.向路由组件传递search参数 */}
{/* <Link
to={`/home/message/detail/?id=${msgobj.id}&title=${msgobj.title}`}
>
{msgobj.title}
</Link> */}
{/* 3.向路由组件传递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>
{/* 路由的两种模式 push和replace */}
{/* <Link
replace={true}
to={{
pathname: "/home/message/detail",
state: { id: msgobj.id, title: msgobj.title },
}}
>
{msgobj.title}
</Link> */}
</li>
)
})}
</ul>
<hr />
{/* 2.声明接收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.backShow}>回退</button>
<button onClick={this.forwardShow}>前进</button>
<button onClick={this.goShow}>go</button>
</div>
)
}
}
3.6 如何在一般组件中使用路由组件的API?(WithRouter)
- WithRouter可以加工一般组件,让一般组件具备 路由组件特有的API
- WithRouter返回的是一个新的组件
- Header组件
import React, { Component } from "react"
import { withRouter } from "react-router-dom"
class Header extends Component {
//回退
backShow = () => {
this.props.history.goBack()
}
//前进
forwardShow = () => {
this.props.history.goForward()
}
//go
goShow = () => {
//2:前进2步;-2:回退2步
this.props.history.go(-2)
}
render() {
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.backShow}>回退</button>
<button onClick={this.forwardShow}>前进</button>
<button onClick={this.goShow}>go</button>
</div>
)
}
}
//withRouter加工后Header会有路由组件的API
export default withRouter(Header)
3.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参数的影响
- BrowserRouter无任何影响,因为history保存在state中
- HashRouter刷新后会导致路由state参数的丢失!!!