五、React路由
大约 6 分钟约 1804 字
(一)相关概念的理解
1.SPA
- 单页 Web 应用(Single Page Web Application, SPA)
- 整个应用只有 一个完整的页面 (index.html) — 单页面多组件
- 点击页面中的链接 不会刷新 页面,只会做页面的 局部更新
- 数据都需要通过 ajax 请求获取,并在前端异步展现
2.路由
1)什么是路由?
- 一个路由就是一个映射关系(
key:value
) key
为路径,value
可能是function
或component
2)路由分类
- 后端路由(以 Node Express 为例)
value
是function
,用来处理客户端提交的请求- 注册路由:
route.get(path, function(req, res))
- 工作过程:当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
/*
请求地址: http://localhost:3000/search/users?q=aa
后台路由
key: /search/users
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);
});
});
- 前端路由
- 底层逻辑是操作浏览器 BOM 对象的
history
属性window.history
- 浏览器端路由:
value
是component
,用于展现页面内容 - 注册路由:
<Route path="/test" component={Test} />
- 工作过程:当浏览器的
path
变为/test
时,当前路由组件就会变为Test
组件
- 底层逻辑是操作浏览器 BOM 对象的
3)react-router-dom
- 文档:https://react-router.docschina.org/
- react-router 库有三种实现
- web:react-router-dom,供 web 开发人员使用
- native:供 React Native 开发人员使用
- anywhere:web 和 native 都可以使用
- react 的一个插件库
- 专门用来实现一个 SPA 应用
- 基于 React 的项目基本都会用到此库
3.组件分类
1)一般组件
- 程序员自定义的组件
- 通过
<Demo />
引入使用 - 一般放在
components
目录下 - 写组件标签时传递了什么,props 就收到什么
2)路由组件
- 路由器匹配的组件
- 通过
<Link to="/demo" component={Demo} />
引入使用 - 一般放在
pages
目录下 - 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"
(二)基本路由使用
1.准备
1)下载 react-router-dom
npm i react-router-dom@5
2)引入 bootstrap.css
<link rel="stylesheet" href="/css/bootstrap.css" />
2.界面分区
- 导航区:渲染路由菜单的区域
- 展示区:渲染当前路由匹配的组件
3.导航区 —— 路由链接
- 将 a 标签改为 Link 标签:
<Link to="/xxx">Demo</Link>
import { Link } from "react-router-dom";
<Link className="list-group-item" to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
4.展示区 —— 注册路由
- 进行路径匹配:
<Route path="/xxx" component={Demo} />
import { Route } from "react-router-dom";
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
5.App 组件最外侧包裹路由器组件
- BrowserRouter:对应 history.createBrowserHistory
localhost:3000/about
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root"),
);
- HashRouter:对应 history.createHashHistory
localhost:3000/#/about
#
后为哈希值,不作为资源传给服务器(localhost:3000)
import { HashRouter } from "react-router-dom";
ReactDOM.render(
<HashRouter>
<App />
</HashRouter>,
document.getElementById("root"),
);
BrowserRouter
与 HashRouter
的区别
(三)1.底层原理不同
- BR 使用的是 H5 的 history API,不兼容 IE9 及以下版本
- HR 使用的是 URL 的哈希值
2.path 表现形式不同
- BR 路径中没有"#"
- 如:localhost:3000/demo/test
- HR 路径中有"#"
- 如:localhost:3000/#/demo/test
state
参数的影响不同
3.刷新后对路由 - BR 没有任何影响,因为
state
保存在history
对象中 - HR 刷新后会导致路由
state
参数丢失
4.备注
- HR 可以用于解决一些路径错误相关的问题
NavLink
与封装 NavLink
(四)NavLink
可以实现路由链接的高亮- 通过
activeClassName
指定样式名
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">
About
</NavLink>
- 标签体内容是一个特殊的标签属性:
children
- 通过
this.props.children
可以获取标签体内容
// components/MyNavLink/index.jsx
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="atguigu"
// className="list-group-item"
// {...this.props}
// >
// {this.props.children}
// </NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" {...this.props} />
);
}
}
// App.jsx
<MyNavLink to="/about">About</MyNavLink>;
Switch
的使用
(五)- 通常情况下,
path
和component
是一一对应的关系 Switch
可以提高路由匹配效率(单一匹配)
// Switch仅匹配第一个符合条件的路由路径
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={TestSwitch} />
</Switch>
(六)解决多级路径刷新页面样式丢失的问题
public/index.html
中引入样式不写 ./
写 /
1.- 常用
<link rel="stylesheet" href="/css/bootstrap.css" />
public/index.html
中引入样式不写 ./
写 %PUBLIC_URL%
2.- 常用,只适用于脚手架
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" />
BrowserRouter
使用 HashRouter
3.路由包裹不使用 - 不推荐
(七)路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配
- 简单记:【输入的路径】必须包含【要匹配的路径】,且顺序要一致
{/* 编写路由链接——在React中靠路由链接实现切换组件 */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
<MyNavLink to="/home/a/b">Home</MyNavLink>
<MyNavLink to="/a/home/b">Home</MyNavLink>
{/* 注册路由 */}
{/* Switch仅匹配第一个符合条件的路由路径 */}
<Switch>
<Route exact path="/about" component={About} />
<Route exact path="/home" component={Home} />
</Switch>
2.开启严格匹配
- 严格匹配不要随便开启,需要再开,有时候开启会导致无法继续匹配二级路由
<Route exact={true} path="/about" component={About} />
<Route exact path="/about" component={About} />
Redirect
的使用
(八)- 一般写在所有路由注册的最下方
- 当所有路由都无法匹配时,跳转到
Redirect
指定的路由
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
(九)嵌套路由
- 注册子路由时要写上父路由的
path
值 - 路由的匹配是按照注册路由的顺序进行的
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
<Redirect to="/home/news" />
</Switch>
(十)向路由组件传递参数
params
参数
1. 1)路由链接
- 携带参数
<Link to="/demo/test/tom/18">详情</Link>
2)注册路由
- 声明接收
<Route path="/demo/test/:name/:age" component={Test} />
Test
组件接收参数
3)const { name, age } = this.props.match.params;
search
参数
2. 1)路由链接
- 携带参数
<Link to="/demo/test/?name=tom&age=18">详情</Link>
2)注册路由
- 无需声明接收,正常注册即可
<Route path="/demo/test" component={Test} />
Test
组件接收参数
3)- 获取到的
search
是urlencoded
编码字符串 - 需要借助
query-string
库解析
import qs from "query-string";
const { search } = this.props.location; // ?name=tom&age=18
const { name, age } = qs.parse(search.slice(1));
state
参数
3. 1)路由链接
- 携带参数
<Link
to={{
pathname: "/demo/test",
state: {
name: "tom",
age: 18,
},
}}
>
详情
</Link>
2)注册路由
- 无需声明接收,正常注册即可
<Route path="/demo/test" component={Test} />
Test
组件接收参数
3)- 当前标签页刷新可以保留住参数
- 因为
BrowserRouter
一直在维护history
属性 - 清空本地缓存后参数丢失
const { name, age } = this.props.location.state || {};
(十一)编程式路由导航
- 借助
this.props.history
对象上的 API 对操作路由跳转、前进、后退this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()