SPA
- 单页面应用 Single Page Application
- 整个应用只有一个完整的页面
- 点击页面中的连接不会刷新页面, 只会做页面的局部刷新
- 数据都用 Ajax 请求, 异步
路由 router
- 路由是一个映射关系 (key: value)
- key 就是地址栏的 path, value 就是对应的组件或者 function
路由分类
后端路由
-
理解: value是function, 用来处理客户端提交的请求。
-
注册路由: router.get(path, function(req, res))
-
工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由
-
浏览器端路由,value是component,用于展示页面内容。
-
注册路由:
<Route path="/test" component={Test}>
-
工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
前端路由基本使用
-
先安装
yarn add react-router-dom@5
, 此处安装的是 5 版本, 用来理解和学习, 后续会补充 6 版本的安装和使用. -
编写路由链接:
- 首先引入
import {Link} from 'react-router-dom'
- 在需要点击跳转的地方编写路由链接
{/* 原生 js 靠 a 标签路由 */} {/* <a className="list-group-item" href="./about.html"> About </a> <a className="list-group-item active" href="./home.html"> Home </a> */} {/* 编写路由链接 */} <Link className="list-group-item" to="/about"> About </Link> <Link className="list-group-item" to="/home"> Home </Link>
- 首先引入
-
注册路由:
- 首先引入
import {Route} from 'react-router-dom'
- 在需要渲染地方注册路由
{/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/>
- 首先引入
-
在 index.jsx 中引入
import {BrowserRouter} from 'react-router-dom'
- 原因是
<Link/>
和<Route/>
都需要被<BrowserRouter/>
来包裹, 所以写在index.js
中相当于包裹整个项目.
ReactDOM.render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode>, document.getElementById('root') );
- 原因是
路由组件与普通组件
普通组件
- 放在 component 文件夹内
- props 不传就没有东西
<Demo/>
路由组件对象的内容
-
放在 pages 文件夹内
-
不点击就不挂载
-
<Route path="/about" component={Demo}/>
-
props 不传也有东西
{{ "history": { go: f go(n) goBack: f, goForward: f, push: f push(path,state) replace: f replace(path,state) }, "location": { "pathname": "/about", "search": "", "hash": "", "key": "p7l20g", state: undefined }, "match": { params: {}, "path": "/about", "url": "/about", } }
NavLink 的使用和封装
基本使用
-
当
Link
标签作为导航并且需要高亮时, 会不太方便. 用NavLink
标签的好处是, 当点击这个路由标签后, 会自动给标签追加一个active
类, 因此可以通过 CSS 来改变标签的样式. -
这里有一个巧合就是 Bootstrap 的高亮显示类也是
active
, 所以使用起来非常方便. -
如果说使用的 CSS UI 库对应的高亮显示类不是
active
就可以在NavLink
内来单独设置高亮显示的类名. 比如高亮显示类名是highlight
, 就可以写activeClassName="highlight"
.<NavLink className="list-group-item" to="/about">About</Link> <NavLink className="list-group-item" to="/home">Home</Link>
-
如果说, 想要单独设置高亮颜色, 绕过已经引入的 UI 库, 可以在 CSS 内设置
!important
语法..active { background-color: yellow !important; color: red !important }
二次封装
-
由于在书写
NavLink
标签的时候, 有大量标签属性重复, 比如className
,activeClassName
或者其他自定义信息, 所以可以进行二次封装来减少代码重复性. -
创建一个新的一般组件
MyNavLink
, 需要引入import { NavLink } from "react-router-dom"
, 同时渲染NavLink
. 通过{...this.props}
来把MyNavLink
收到的 props 传到NavLink
的 props 中去.render() { return ( <NavLink className="list-group-item" {...this.props} /> ) }
-
在实际挂载
MyNavLink
的时候, 通过标签内的属性来给组件传递 props. 这里注意: 标签内容也会作为 props 中的 children 传递到组件中去. 传递后,NavLink
会将收到的 props.chidren 渲染到生成的标签内容.- MyNavLink 标签内容 -> MyNavLink.props.children -> NavLink.props.Children -> NavLinks标签内容
-
这样的好处就是, 二次封装后, MyNavLink 组件依然可以写成标签➕闭合标签的形式, 并且在标签内容内写需要的内容, 从而与 NavLink呼应.
Switch 的作用
注册路由的时候, 可以引入 import {Switch} from "react-router-dom"
包裹在所有 <Route>
标签的外侧.
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={About} />
</Switch>
这样可以避免一种情况:
相同地址下, 如果多传了一个路由组件建, 会同时显示两个组件.
样式丢失的解决
-
在 react 服务器下, 如果访问一个不存在的地址, 或者是前端路由地址, react 会默认返回 public 中的 index.html 页面. 这个时候会出现样式丢失的情况.
-
发生这种情况的原因是:
-
在 index.html 中引入的 css 路径如下:
<link rel="stylesheet" href="./css/bootstrap.css" />
-
正常情况下加载 css 文件是从根目录开始的:
http://localhost:3000/css/bootstrap.css
-
而直接输入不存在地址,或者是前端路由地址, 加载 css 是从路由地址开始的:
http://localhost:3000/notexist/css/bootstrap.css
-
localhost: 3000
对应的是 pubic 文件夹. 而 public 文件夹中并不存在 notexist 文件夹. 所以 css 会加载失败.
-
-
解决办法:
- 更改 css 引入路径为绝对路径
<link rel="stylesheet" href="/css/bootstrap.css" /> 或者 <link rel="manifest" href="%PUBLIC_URL%/css/bootstrap.css" />
- 改 BrowserRouter 为 HashRouter
路由的模糊匹配和精准匹配
模糊匹配
如果编写的路由链接大于或者说包含注册路由, 则跳转到包含的注册路由.
{/* 编写路由链接 */}
<MyNavLink to="/fanxuan/about">
About
</MyNavLink>
<MyNavLink to="/fanxuan/home/a/b">
Home
</MyNavLink>
{/* 注册路由 */}
<Switch>
<Route path="/fanxuan/about" component={About} />
<Route path="/fanxuan/home" component={Home} />
</Switch>
精准匹配
正常来说不开启精准匹配, 有时候开启无法继续匹配二级路由.
{/* 编写路由链接 */}
<MyNavLink to="/fanxuan/about">
About
</MyNavLink>
<MyNavLink to="/fanxuan/home/a/b">
Home
</MyNavLink>
{/* 注册路由 */}
<Switch>
<Route exact path="/fanxuan/about" component={About} />
<Route exact path="/fanxuan/home" component={Home} />
</Switch>
Rediect 的使用
在没有匹配到任何路由链接时, 比如直接输入网站地址需要跳转的情况下. 就可以设置 <Redirect>
标签进行跳转.
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about"/>
</Switch>
路由的嵌套
-
路由的注册的是有先后顺序的, App.jsx 中的路由先注册, 先生效.
-
二级路由渲染页面的时候, 需要渲染一级路由的组件, 再渲染二级路由的组件. 一级路由被渲染的原因是出发了路由的模糊匹配.
-
做默认显示的时候, 可以用
<Redirect>
来匹配页面. 挂载了二级路由后, 发现二级路由为空, 则匹配<Redirect>
.
一级路由
{/* 编写路由链接 */}
<MyNavLink to="/about" >
About
</MyNavLink>
<MyNavLink to="/home">
Home
</MyNavLink>
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
二级路由
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news"> News </MyNavLink>
</li>
<li>
<MyNavLink to="/home/messages"> Messages </MyNavLink>
</li>
</ul>
<Switch>
<Route path="/home/messages" component={Messages} />
<Route path="/home/news" component={News} />
<Redirect to="/home/news" />
</Switch>
</div>
向路由组件传递参数
向路由组件传递 params 参数
- 传递
{/* 向路由组建传递 params 参数 */}
<Link to={`/home/messages/details/${message.id}/${message.title}`}>
{message.title}
</Link>
- 声明接收
{/* 声明接收 params 参数 */}
<Route path="/home/messages/details/:id/:title" component={Details} />
- 接收
//接收 params 参数
const {id, title} = this.props.match.params
向路由组件传递 serach 参数
- 传递
{/* 向路由组建传递 serach 参数 */}
<Link to={`/home/messages/details/?id=${message.id}&title=${message.title}`}>
{message.title}
</Link>
- 无需声明接收
{/* search 参数无需声明接收 */}
<Route path="/home/messages/details" component={Details} />
- 接收
- 由于接收到的参数形式是
urlencoded
也就是 querystring 形式 : key=value&key=value: urlencoded, 所以为了方便, 可以引入一个 react 自带的库来转化成对象. - 引入
import qs from 'qs'
, 主要的两个方法qs.Stringify(obj)
: 对象转 qs,qs.Parse(qs.slice(1))
: qs转对象.
- 由于接收到的参数形式是
const {search} = this.props.location
const result = qs.parse(search.slice(1))
const {id, title} = result
const findRes = DetialData.find((item) => item.id === id)
向路由组建传递 state 参数
不会在地址中显示穿的参数, 但刷新页面, 依然带着参数. 如果删除缓存, state 变为 undefined.
- 传递
{/* 向路由组建传递 state 参数 */}
<Link to={{pathname:'/home/messages/details',state:{id:message.id,title:message.title}}}>
{message.title}
</Link>
- 声明接收
{/* state 参数无需声明接收 */}
<Route path="/home/messages/details" component={Details} />
- 接收
const {id, title} = this.props.location.state
const findRes = DetialData.find((item) => item.id === id)
push与 replace 的区别
在编写路由连接的时候, 可以加 replace=true
来标识路由为 replace 模式. replace 模式不会将访问存在于历史记录, 后退函数也拿不到历史记录. 历史记录类似于一个栈结构, replace 是直接换栈顶的内容.
//replace=true 可直接简写为 true
<MyNavLink replace to="/home/messages"> Messages </MyNavLink>
编程式路由导航
编程式就是直接调用 props.history 中的函数api前进或者后退.
pushClick = (id,title) => {
this.props.history.push('/home/messages/details',{ id, title })
}
replaceClick = (id,title) => {
this.props.history.replace('/home/messages/details',{ id, title })
}
forward = () => {
this.props.history.goForward()
}
back = () => {
this.props.history.goBack()
}
go = () => {
this.props.history.go(2)
}
render() {
const { messagesArray } = this.state
return (
<div>
<ul>
{messagesArray.map((message) => {
return (
<li key={message.id}>
{/* 向路由组建传递 state 参数 */}
<Link to={{ pathname: '/home/messages/details', state: { id: message.id, title: message.title } }}>
{message.title}</Link>
<button onClick={() => { this.pushClick(message.id, message.title) }}>push</button>
<button onClick={() => { this.replaceClick(message.id, message.title) }}>replace</button>
</li>
)
})}
</ul>
<hr />
{/* state 参数无需声明接收 */}
<Route path="/home/messages/details" component={Details} />
<button onClick={this.back}> 回退</button>
<button onClick={this.forward}> 前进 </button>
<button onClick={this.go}> 前进两页 </button>
</div>
)
}
}
withRouter 的使用
withRouter
函数可以接收一个一般组件, 将其转化为带有路由属性的组件(props 里有 history,match,location)
import React, { Component } from "react"
import { withRouter } from "react-router-dom"
class Header extends Component {
forward = () => {
this.props.history.goForward()
}
back = () => {
this.props.history.goBack()
}
go = () => {
this.props.history.go(2)
}
render() {
return (
<div className="page-header">
<button onClick={this.back}> 回退</button>
<button onClick={this.forward}> 前进 </button>
<button onClick={this.go}> 前进两页 </button>
<h2>React Router Demo</h2>
</div>
)
}
}
export default withRouter(Header)