react-router
路由的概念在软件工程中出现,最早是在后端路由中实现的,原因是web的发展主要经历了以下几个阶段:
1,后端路由阶段:服务器渲染好所有页面并且根据不同的请求路径将渲染好的页面返回给客户端进行展示,这种情况下渲染好的页面不再需要单独加载任何的js和css。
缺点: 一种情况是整个页面的模块需要由后端人员来编写和维护,另外一种情况是前端开发人员如果要开发页面需要通过PHP和Java等语言来编写页面代码,而且通常情况下将HTML代码和对应的逻辑混在一起, 编写和维护都是很麻烦的事情。
2,前后端分离阶段:这时会将html、css以及js文件请求回来在前端进行合成渲染,而此时的后端只是负责提供API了,只不过客户端的每一次请求都会重复从静态资源服务器请求文件。
3,单页面富应用:整个web应用实际上只有一个页面,当URL发生改变时并不会从服务器去重复请求静态资源,而是通过js监听URL的改变从而根据URL的不同去渲染不同的页面组件。监听URL发生改变,同时不引起页面的刷新有两个方法:
(1)通过URL的hash改变URL。
(2)通过HTML5的history模式修改URL。
hash模式的原理
本质上是监听window的hashchange事件,然后根据具体的location.hash值来判断所要显示的内容;注意:hash模式的优势就是兼容性更好,在老版本的IE都可以运行,但是缺陷就是路径后有一个#,让用户觉得不像一个真实的路径。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=, initial-scale=1.0" />
<title>秃头的科比</title>
</head>
<body>
<div>
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div class="router-view"></div>
</div>
<script>
let routerViewEl = document.getElementsByClassName("router-view")[0];
window.addEventListener("hashchange", function () {
switch (location.hash) {
case "#/home":
routerViewEl.innerHTML = "首页";
break;
case "#/about":
routerViewEl.innerHTML = "关于";
break;
default:
routerViewEl.innerHTML = "";
}
});
</script>
</body>
</html>
history模式的原理
history模式是HTML5新增的,它有6种方法修改URL而不引起页面的重新刷新:
(1)replaceState:替换原来的路径。
(2)pushState:使用新的路径。
(3)popState:路径的回退。
(4)go:向前或向后改变路径。
(5)back:向后改变路径。
(6)forward:向前改变路径。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>history原理</title>
</head>
<body>
<div id="app">
<a href="/home">首页</a>
<a href="/about">关于</a>
<div class="router-view"></div>
</div>
<script>
let routerViewEl = document.getElementsByClassName("router-view")[0];
// 遍历获取所有的a元素并添加监听事件, 从而自己来监听a元素的改变
let aEls = document.getElementsByTagName("a");
for (let el of aEls) {
el.addEventListener("click", (e) => {
e.preventDefault();
let href = el.getAttribute("href");
history.pushState({}, "", href);
urlChange();
});
}
// 执行回退操作时, 依然来到urlChange
window.addEventListener("popstate", urlChange);
// 监听URL的改变
function urlChange() {
//根据location.pathname来判断所要显示的内容
switch (location.pathname) {
case "/home":
routerViewEl.innerHTML = "首页";
break;
case "/about":
routerViewEl.innerHTML = "关于";
break;
default:
routerViewEl.innerHTML = "";
}
}
</script>
</body>
</html>
版本问题
react-router从版本4开始,路由不再集中在一个包中进行管理了:
(1)react-router是router的核心部分代码。
(2)react-router-dom是用于浏览器的(在项目中使用时,只需要下载它即可,因为它会帮你自动下载 react-router)
(3)react-router-native是用于原生应用的。
虽然目前我们使用最新的react-router版本是5版本:实际上4和5版本的区别并不大。
react-router常用API
1,BrowserRouter和HashRouter:
(1)BrowserRouter使用history模式。
(2)HashRouter使用hash模式。
2,Link和Navlink:通常路径的跳转使用Link组件,其最终会被渲染成a元素,NavLink只不过是可以在Link的基础上可以添加一些样式属性而已,to属性用于设置将要跳转到的路径。需求:路径选中时,对应的a元素变为红色。
(1)activeStyle:活跃时匹配的样式;
<NavLink exact to="/" activeStyle={{color: "red", fontSize: "30px"}}>首页</NavLink>
<NavLink to="/about" activeStyle={{color: "red", fontSize: "30px"}}>关于</NavLink>
<NavLink to="/profile" activeStyle={{color: "red", fontSize: "30px"}}>我的</NavLink>
(2)activeClassName:活跃时添加的class,事实上在默认匹配成功时,NavLink就会自动给a标签添加上.active属性,但是为了避免样式重叠,推荐自定义class。
<NavLink exact to="/" activeClassName="link-active">首页</NavLink>
<NavLink to="/about" activeClassName="link-active">关于</NavLink>
<NavLink to="/profile" activeClassName="link-active">我的</NavLink>
//推荐重新自定义样式
a.active, a.link-active {
color: red;
font-size: 30px;
}
以下是实现路由跳转的几种方式
方式一:使用Route组件实现路由的跳转,route组件常用属性:
(1)exat:精准匹配,只要匹配到完全一致的路径,才会渲染对应的页面组件。
(2)path属性:用于设置匹配到的路径。
(3)component属性:设置匹配到的页面组件。
(4)switch的作用:当我们匹配到某一个路径时,我们会发现有一些问题:
实际开发中,我们往往希望有一种排他的思想,只要匹配到了第一个,那么后面的就不应该继续匹配了,这个时候我们可以使用Switch来将所有的Route组件进行包裹。
<Switch>
<Route exact path="/" component={Home} />
<Route component={NoMatch} />
</Switch>
(5)redirect的使用
Redirect用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
案例: 用户跳转到User界面;但是在User界面有一个isLogin用于记录用户是否登录:
true:那么显示用户的名称;
false:直接重定向到登录界面;
render() {
return this.state.isLogin ? (
<div>
<h2>User</h2>
<h2>用户名: coderwhy</h2>
</div>
): <Redirect to="/login"/>
}
方式二:借助react-router实现路由的跳转
目前我们所有的路由定义都是使用Route组件或者手动实现路由的跳转的,但是这两种方式会让路由变得非常混乱,因此我们希望将所有的路由配置放到一个地方进行集中管理,这个时候便可以使用react-router-config来完成; 安装react-router-dom和react-router-config,配置路由映射的关系,使用renderRoutes函数完成配置。
//相关的路由映射关系就不再给出了,主要需要注意以下两点的使用
//一级路由
{renderRoutes(routes)}
//子路由
{renderRoutes(this.props.route.routes)}
方式三:最后也可以手动实现路由的跳转
实际上我们也可以通过js代码手动实现跳转,但是通过js代码进行手动跳转时有一个前提就是必须获取到history对象,那么如何获取到history对象呢?
(1)如果该组件是通过路由直接跳转过来的,那么可以直接获取history、location、match等对象。
(2)如果该组件是一个通过route普通渲染的组件,那么不可以直接获取history、location、match等对象,那么如果是通过route渲染的普通组件希望获取对应的对象属性应该怎么做呢? 前面我们学习过高阶组件,可以在组件中添加想要的属性,react-router也正是通过高阶组件为我们的route组件添加相关的属性的,但是如果想要在App组件中获取到history对象,必须满足条件:App组件必须包裹在Router组件之内并且使用withRouter高阶组件包裹app组件导出。
import React, { PureComponent } from 'react';
import Product from './pages/product';
class App extends PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button onClick={e => this.jumpToProduct()}>商品</button>
</div>
)
}
jumpToProduct() {
//获取到history对象从而实现跳转
this.props.history.push("/product");
}
}
//导出时需要通过withRouter进行一个包裹使得可以获取到history对象
export default withRouter(App);
以下是使用路由进行参数传递的几种方式
方式一:使用动态路由的方式
动态路由的概念指的是路由中的路径并不会固定,比如/detail的path对应一个组件Detail,而如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route并且进行显示, 这个匹配规则我们就称之为动态路由,通常情况下,我们使用动态路由传递一些简单的参数。 只不过在这里需要特别注意一下,如何获取到传递过来的参数。
<NavLink to={`/detail/${id}`} activeClassName="link-active">详情</NavLink>
<Switch>
<Route path="/detail/:id" component={Detail} />
</Switch>
//那么如何在组件中拿到这个id呢
render() {
//先拿到match对象
const match = this.props.match;
console.log(match.params);
return (
<div>
<h2>Detail: {match.params.id}</h2>
</div>
)
}
方式二:使用search传递参数(这种方式不推荐使用)
<NavLink to={`/detail2?name=why&age=18`} activeClassName="link-active">详情2</NavLink>
<Switch>
<Route path="/detail2" component={Detail2} />
</Switch>
export default class Detail2 extends PureComponent {
render() {
console.log(this.props.location);
return (
<div>
<h2>Detail2: {this.props.location.search}</h2>
</div>
)
}
}
方式三:使用NavLink中给to属性传入对象
<NavLink to={{
pathname: "/detail3",
search: "name=abc",
state: info
}}
activeClassName="link-active">
详情</NavLink>
<Switch>
<Route path="/detail3" component={Detail3} />
</Switch>
export default class Detail3 extends PureComponent {
render() {
const location = this.props.location;
console.log(location);
return (
<div>
<h2>Detail3: {location.state.name}</h2>
</div>
)
}
}