当前位置:   article > 正文

三分钟实现一个react-router-dom5.0的路由拦截(导航守卫)

三分钟实现一个react-router-dom5.0的路由拦截

不同于vue,通过在路由里设置meta元字符实现路由拦截。在使用 Vue ,框架提供了路由守卫功能,用来在进入某个路有前进行一些校验工作,如果校验失败,就跳转到 404 或者登陆页面,比如 Vue 中的 beforeEnter 函数:

  1. ...
  2. router.beforeEach(async(to, from, next) => {
  3. const toPath = to.path;
  4. const fromPath = from.path;
  5. })
  6. ...

react实现路由拦截的基本思路还是利用Route 的render函数。通过判断拦截条件来实现不同的组件的跳转,从而实现拦截。在之前的版本中,React Router 也提供了类似的 onEnter 钩子,但在 React Router 4.0 版本中,取消了这个方法。React Router 4.0 以后采用了声明式的组件,路由即组件,要实现路由守卫功能,就得我们自己去写了。
如果不使用路由守卫,Router 组件是这样子的:

  1. import * as React from 'react';
  2. import { HashRouter,Switch,Route,Redirect } from 'react-router-dom';
  3. import Index from "./page/index";
  4. import Home from "./page/home";
  5. import ErrorPage from "./page/error";
  6. import Login from "./page/login";
  7. export const Router = () => (
  8. <HashRouter>
  9. <Switch>
  10. <Route path="/" exact component={Index}/>
  11. <Route path="/login" exact component={Login}/>
  12. <Route path="/home" exact component={Home}/>
  13. <Route path="/404" exact component={ErrorPage}/>
  14. <Redirect to="/404" />
  15. </Switch>
  16. </HashRouter>
  17. );

上面的 Router 组件,包含了三个页面:

  • 登陆
  • 主页
  • 404 页面

以及四个路由:

  • 根路由
  • 登陆路由
  • 主页路由
  • 404 路由

其中,根路由和 /home 路由,都定向到了主页路由。
以上是一个基本的路由定义,可以在登陆/主页和 404 页面之间来回跳转,但也有一些问题:

  • 非登陆状态下,可以直接跳转到主页
  • 登陆状态下,也可以输入 /login 路由跳转到登录页

现在,我们想完成这样的功能:

  • 非登陆状态下,无法直接跳转到主页,如果在非登陆状态下进行主页跳转,需要重定向至登陆路由
  • 登陆状态下,无法跳转至登录页,如果在登陆状态下进行登陆页跳转,需要重定向至主页路由

要完成这个功能,有两种方案:

  • 在每个组件中,根据 props 上的 history 对象来进行跳转
  • 进行全局的路由守卫处理

首先在跟目录src下创建一个routerMap.js文件,代码如下: 

 

  1. import Index from "./page/index";
  2. import Home from "./page/home";
  3. import ErrorPage from "./page/error";
  4. import Login from "./page/login";
  5. export default [
  6. { path: "/", name: "App", component: Index, auth: true },
  7. { path: "/home", name: "Home", component: Home, auth: true },
  8. { path: "/login", name: "Login", component: Login },
  9. { path: "/404", name: "404", component: ErrorPage },
  10. ];

将 auth 设置为 true,表示该路由需要权限校验。
然后,定义 Router 组件,在App.js中,该组件是经过高阶组件包装后的结果:

  1. //App.js
  2. import React, { Component } from "react";
  3. import { BrowserRouter as Router, Switch } from "react-router-dom";
  4. import FrontendAuth from "./FrontendAuth";
  5. import routerMap from "./routerMap";
  6. class App extends Component {
  7. // eslint-disable-next-line no-useless-constructor
  8. constructor(props) {
  9. super(props);
  10. }
  11. render() {
  12. return (
  13. <Router>
  14. <div>
  15. <Switch>
  16. <FrontendAuth routerConfig={routerMap} />
  17. </Switch>
  18. </div>
  19. </Router>
  20. );
  21. }
  22. }
  23. export default App;

所有的路由跳转,都交给 FrontendAuth 高阶组件代理完成。下面是 FrontendAuth 组件的实现:

  1. import React, { Component } from "react";
  2. import { Route, Redirect } from "react-router-dom";
  3. class FrontendAuth extends Component {
  4. // eslint-disable-next-line no-useless-constructor
  5. constructor(props) {
  6. super(props);
  7. }
  8. render() {
  9. const { routerConfig, location } = this.props;
  10. const { pathname } = location;
  11. const isLogin = sessionStorage.getItem("username");
  12. console.log(pathname, isLogin);
  13. console.log(location);
  14. // 如果该路由不用进行权限校验,登录状态下登陆页除外
  15. // 因为登陆后,无法跳转到登陆页
  16. // 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
  17. const targetRouterConfig = routerConfig.find(
  18. (item) => item.path === pathname
  19. );
  20. console.log(targetRouterConfig);
  21. if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
  22. const { component } = targetRouterConfig;
  23. return <Route exact path={pathname} component={component} />;
  24. }
  25. if (isLogin) {
  26. // 如果是登陆状态,想要跳转到登陆,重定向到主页
  27. if (pathname === "/login") {
  28. return <Redirect to="/" />;
  29. } else {
  30. // 如果路由合法,就跳转到相应的路由
  31. if (targetRouterConfig) {
  32. return (
  33. <Route path={pathname} component={targetRouterConfig.component} />
  34. );
  35. } else {
  36. // 如果路由不合法,重定向到 404 页面
  37. return <Redirect to="/404" />;
  38. }
  39. }
  40. } else {
  41. // 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
  42. if (targetRouterConfig && targetRouterConfig.auth) {
  43. return <Redirect to="/login" />;
  44. } else {
  45. // 非登陆状态下,路由不合法时,重定向至 404
  46. return <Redirect to="/404" />;
  47. }
  48. }
  49. }
  50. }
  51. export default FrontendAuth;

创建登录的界面src/page/login.js,代码如下:

  1. import React, { Component } from "react";
  2. import { Redirect } from "react-router-dom"; //重定向使用
  3. class Login extends Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = {
  7. username: "",
  8. password: "",
  9. rediectToReferrer: false, // 是否重定向之前的界面
  10. };
  11. this.handleChange = this.handleChange.bind(this);
  12. this.handleSumit = this.handleSumit.bind(this);
  13. }
  14. // 处理用户名、密码的变化
  15. handleChange(e) {
  16. if (e.target.name === "username") {
  17. this.setState({
  18. username: e.target.value,
  19. });
  20. } else if (e.target.name === "password") {
  21. this.setState({
  22. password: e.target.value,
  23. });
  24. }
  25. }
  26. // 提交登录表单
  27. async handleSumit(e) {
  28. e.preventDefault();
  29. const username = this.state.username;
  30. const password = this.state.password;
  31. if (username.length === 0 || password.length === 0) {
  32. alert("用户名或密码不能为空!");
  33. return;
  34. }
  35. // 保存信息到sessionStorage
  36. sessionStorage.setItem("username", username);
  37. // 登录成功后,设置redirectToReferrer为true;
  38. this.setState({
  39. rediectToReferrer: true,
  40. });
  41. let RedirectUrl = this.props.location.state
  42. ? this.props.location.state.from.pathname
  43. : "/";
  44. // 登陆成功之后的跳转
  45. this.props.history.push(RedirectUrl);
  46. }
  47. render() {
  48. return (
  49. <form className="login" onSubmit={this.handleSumit}>
  50. <div>
  51. <label htmlFor="">
  52. 用户名:
  53. <input
  54. type="text"
  55. name="username"
  56. value={this.state.username}
  57. onChange={this.handleChange}
  58. />
  59. </label>
  60. </div>
  61. <div>
  62. <label htmlFor="">
  63. 密码:
  64. <input
  65. type="password"
  66. name="password"
  67. value={this.state.password}
  68. onChange={this.handleChange}
  69. />
  70. </label>
  71. </div>
  72. <input type="submit" value="登录" />
  73. </form>
  74. );
  75. }
  76. }
  77. export default Login;

全部代码目录如下:

页面上的路由跳转,都由 FrontendAuth 高阶组件代理了,在 Switch 组件内部,不再是 Route 组件,而只有一个 FrontendAuth 组件。
FrontendAuth 组件接收一个名为 config 的 Props,这是一份路由表。同时,由于 FrontendAuth 组件放在了 Switch 组件内部,React Router 还自动为 FrontendAuth 注入了 location 属性,当地址栏的路由发生变化时,就会触发 location 属性对象上的 pathname 属性发生变化,从而触发 FrontendAuth 的更新(调用 render 函数)。
FrontendAuth 的 render 函数中,根据 pathname 查找到路由表中的相关配置,如果该配置中指定了无需校验,就直接返回相应的 Route 组件。
如果查找到的配置需要进行校验,再根据是否登陆进行处理,具体可以查看代码中的注释。

总结一下,实现路由守卫需要考虑到以下的问题:

  1. 未登录情况下,访问不需要权限校验的合法页面:允许访问
  2. 登陆情况下,访问登陆页面:禁止访问,跳转至主页
  3. 登陆情况下,访问除登陆页以外的合法页面:允许访问
  4. 登陆情况下,访问所有的非法页面:禁止访问,跳转至 404
  5. 未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页
  6. 未登录情况下,访问所有的非法页面:禁止访问,跳转至 404

源码链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/121466
推荐阅读
相关标签
  

闽ICP备14008679号