一、安装React Router
目前学习的是React-Router-DOM 5版本,所以我们需要下载5版本的路由插件
npm install react-router-dom@5
二、React路由的模式
React-Router的模式是History模式,因为在React中,一切皆组件,所以React-Router也是以组件的形式解构出来使用。
important React from "react";
// 我们从react-router-dom中解构出来了四个组件
// 1.BrowserRouter 这个组件是放置在总入口文件中包住App或者别的什么名字的父组件的组件,只有用这个组件包住总的父组件,我们才可以在这个父组件下使用路由,as是取别名的意思
// 2.Switch 这个组件的作用是分支匹配,可以将5版本路由的包容性模式变成排他性模式,主要是用来包住Router组件
// 3.Link 这个组件是作用在子组件中的,我们用这个标签中需要写内容,不然跳转标签不能显示出来,to属性表示我们跳转的url
// 4.Router 这个写在子组件中,是路由跳转后显示的组件,path属性表示跳转到哪里时激活这个标签,component属性表示激活该标签时显示什么组件
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
class App extends React.Component {
state = {};
render() {
return (
<h2>路由基础</h2>
<ul>
<li>
<Link to="/home">首页</Link>
</li>
<li>
<Link to="/about">关于页</Link>
</li>
<li>
<Link to="/mine">我的</Link>
</li>
</ul>
<hr />
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/mine" component={Mine} />
)
}
}
React-Router 5版本的路由模式是包容性路由,模糊匹配,6版本和Vue-Router一样都是排他性路由。
模糊匹配:意思就是,只要斜杠之前的部分可以完全匹配,那么不管斜杠后面加了什么东西,都能一直匹配到这个路由链接,例如:/home和/home/index,这两个都会匹配到/home指向的组件,所以这就会导致一个问题,如果我们希望首页的匹配只需要 “/“ ,那么我们一旦这么做了,并且把首页的Route组件放在最上面,那么不管我们切换到别的什么路由组件,都会一直匹配到首页这个路由组件。
包容性路由:路由表会自上而下匹配,遇到匹配项后,仍会向下继续匹配,直到整个路由表结束,而排他性路由,只要遇到一个匹配项,就会从这里终止匹配,不会继续匹配下面的路由。
那么怎么解决这个问题呢?
我们可以用Switch组件包住Route组件,将包容性路由变为排他性路由,并且在Route组件中有一个属性exact(精准匹配),就不会再模糊匹配了,但是exact有个问题,如果是嵌套路由,该路由组件下还有子路由,精准匹配会导致路由的pathname与父路由组件的名字不符,导致父路由丢失,子路由也就无法跳转了
三、动态路由
通过Router组件切换的路由,我们可以在组建的props中找到许多路由注入的数据和方法,我们可以通过这些数据和方法获取动态参数和编程式导航
四、路由的渲染方法
我们除了在Route组件的component属性中加入子组件这个方法渲染路由外,还可以使用render方法、children方法
1、component
import {Link,Route} from "react-router-dom";
import Home from "./Home.jsx";
<Link to="/home">首页</Link>
<Route path="/home" component={Home}></Route>
2、render
render属性后面接一个箭头函数,箭头函数返回一个组件来渲染路由。
render函数只能返回工厂函数组件,不能返回类组件。
render函数的好处是我们可以在箭头函数里写各种表达式,自由判断决定我们的返回值。在后面的路由鉴权等都可以通过这个箭头函数来判断我们要返回的数据或组件。
render方法中,我们直接写箭头函数会导致子组件的props中没有路由信息,所以我们需要在箭头函数左边传过去一个props参数,并在右边返回的组件上解构,这样在返回的组件中就有路由信息了
import {Link,Route} from "react-router-dom";
import Home from "./Home.jsx";
<Link to="/home">首页</Link>
<Route path="/home" render={(props)=><Home {...props} />}></Route>
3、children
和render属性的方法类似,在有Switch组件包裹的情况下,children和render的效果是一模一样的,但如果没有Switch组件包裹时,children写的路由组件无论url是否匹配都会渲染。
import {Link,Route} from "react-router-dom";
import Home from "./Home.jsx";
<Link to="/home">首页</Link>
<Route path="/home" children={(props)=><Home {...props} />}></Route>
4、插槽渲染组件 ——方法一Route组件
除了在Route组件的属性中写入要渲染的组件外,我们还可以直接在Router的插槽中插入要渲染的组件,但是正常的情况下这种插槽路由在组件中是没有没有信息的
import {Link,Route} from "react-router-dom";
import Home from "./Home.jsx";
<Link to="/home">首页</Link>
<Route path="/home">
<Home />
</Route>
5、插槽渲染组件 ——方法二withRoute高阶组件
我们可以在拿不到路由信息的组件外面放入一个高阶组件中加工一下后,就可以正常拿到路由信息了。
对于工厂函数组件,我们可以在定义的时候直接将右边的箭头函数直接放进withRoute高阶组件中。
对于类组件,我们可以用装饰器在class上面紧邻的一行写一个@withRoute装饰一下暴露的组件。
也可以在插入插槽时使用。
import {Link,Route,withRoute} from "react-router-dom";
import Home from "./Home.jsx";
<Link to="/home">首页</Link>
<Route path="/home">
{withRoute(Home)}
</Route>
五、路由重定向
路由重定向我们需要从react-router-dom中再解构出一个组件,Redirect
Redirect组件中的from属性后面是我们需要重定向额url,to属性是我们重定向到的那个url,然后需要加一个exact精准匹配,避免将后面的地址也重定向至to属性指向的url。
TIPS:
在嵌套路由中,要注意不要在子组件中重复写Redirect重定向,因为重定向子组件时,会重新匹配父路由,不然无法找到子路由,而匹配到父路由时,触发了父路由的重定向,然后又再次触发子路由的重定向,这样就会进入死循环导致报错。
import {Redirect} from "react-router-dom";
<Redirect from"/" to="/home" exact></Redirect>
六、路由鉴权
路由鉴权需要使用到Route组件的render属性和路由重定向的功能,因为如果我们正常render里面鉴权失败跳转到登录页,是直接将登录页组件放入else语句中return,我们会发现URL还是我们点击前往的那个页面,把那个没有改变为登录页的url,这是我们就需要使用路由重定向,将鉴权失败后重定向至登录页的url。
import {Link,Route,withRoute} from "react-router-dom";
import Home from "./Home.jsx";
import Login from "./Login.jsx";
<Link to="/home">首页</Link>
<Route path="/home" render={(props)=>{
if(localStorage.getItem("token")) {
// 如果token验证成功,返回首页组件
return <Home />;
} else {
// 这里的from可以省略,因为我们不需要判断他从哪里来
// 如果token验证失败,重定向至登录页
return <Redirect to="/login"></Redirect>
}
}}></Route>
<Route path="/login" component={Login}></Route>
打包
如果每一个url的鉴权都这样写,会显得代码不够美观而且冗长,所以我们需要将这个鉴权过程打包成一个组件,通过传参的方式重复使用
import React from "react";
import {Route} from "react-router-dom";
class Auth extends React.Component {
render() {
// 将跳转的地址作为属性传给该路由组件
return <Route path={this.props.path} render={(props)=>{
if(localStorage.getItem("token")) {
// 如果token验证成功,返回当作插槽插入的希望被渲染的组件
return this.props.children;
} else {
// 这里的from可以省略,因为我们不需要判断他从哪里来
// 如果token验证失败,重定向至登录页
// 这里的to可以传递一个对象过去,可以传递的信息和路由信息location里的一样
return <Redirect to={{
pathname:"/login",
}}></Redirect>
}
}}></Route>
}
}
export default Auth;
如果我们想在登陆成功后跳转回之前那个页面,我们可以在鉴权失败跳转到登录页时,传递一个从哪个页面跳转过去的这个信息给登录页的路由,这个时候我们就可以创建一个state属性,作为传递这个信息的对象属性,具体这样做:
```
if(localStorage.getItem("token")) {
return <Home />;
} else {
return <Redirect to={{
pathname:"/login",
state:{
from:this.props.path
}
}}></Redirect>
}
state属性传递的参数在url上不可见,但浏览器可以读取到,并且因为该属性是存储在url上的,所以刷新浏览器数据也不会丢失。
### 七、NavLink
和Link组件有完全相同的功能,并且多了一个高亮的功能
1、这是因为NavLink创建的跳转标签多了一个class类名,默认的类名为active,我们就可以通过这个类名对标签的样式进行修改;
2、NavLink还有一个属性activeClassName,这个属性可以修改默认的类名。
### 八、路由跳转保护(弹出框)
#### Prompt组件
1、Prompt组件也是从react-router-dom中解构出来的;
2、Prompt组件有两个属性,一个是when,一个是message,when是弹出条件,也就是什么时候这个组件会弹出来,message是提示信息,如果用户点确定,那么仍会离开,如果用户点否,则会取消跳转。
```plain
import {Prompt} from "react-router-dom";
<Prompt when={//某个条件} message="你确定要离开吗?" />
九、404页面
在路由组件最下面加一个Route组件,path的值设为”*“号,写404路由时,一定要加Switch,并且必须放在最后面
<Switch>
<Route path="/home" component={Home}></Route>
...
<Route path="*" component={NotFound}></Route>
</Switch>
十、路由的hooks
16.8版本的时候,react新增了hooks功能,增强了工厂函数组件的性能,所以现在的新项目基本都是使用工厂函数组件而不是使用类组件了。
在项目中过多的使用高阶组件的包装,通常都会让项目的层级变深,在路由中,我们可以使用几个路由的hooks来简化我们的代码(组件树的层级)。
路由的几个hooks可以直接从react-router-dom中解构出来
useHistory,uesLocation,useRouteMatch,useParams
函数组件要放在组件的顶层调用,也就是最外层。
1、useHistory
路由的history对象
2、uesLocation
路由的location对象
3、useRouteMatch
路由的match对象
4、useParams
路由的params对象
十一、创建路由表 ——方法一
我们将App作为一个组件引到一个组件中,再将这个组件当成App引到主入口文件中,相当于在App外面嵌套了一层结构,然后我们保留App中的Switch组件,并将里面路由信息作为App的插槽内容放在外面嵌套的那个组件中,而App本身的Switch组件只需要接收一个this.props.children(插槽内容),这样相当于就把路由表剥离出来了,更方便维护。
// routes.jsx
import {Route} from "react-router-dom";
import React from "react";
// 引入组件
import App from "./App"
import Home from "./Home";
import About from "./About";
import Home1 from "./Home1";
import Home2 from "./Home2";
class Routes extends React.Component {
render() {
return (
<App>
<Route path="/home">
<Home>
<Route path="/home/home1">
<Home1></Home1>
</Route>
<Route path="/home/home2">
<Home2></Home2>
</Route>
</Home>
</Route>
<Route path="/about">
<About></About>
</Route>
</App>
)
}
}
// App.jsx
import {Switch,Link} from "react-router-dom";
import React from "react";
class Routes extends React.Component {
render() {
return (
<>
<div>路由表</div>
<ul>
<li><Link to="/home">首页</Link></li>
<li><Link to="/about">关于页</Link></li>
</ul>
<Switch>{this.props.children}</Switch>
</>
)
}
}
只需要不断将新的路由作为插槽插入新的组件中,在对应的组件中放入一个Switch组件接收这些插槽内容,就可以做到路由表从组件的剥离。