Vue和React对比学习之路由拦截鉴权

2,973 阅读10分钟

简介

哈喽大家好,我是苏苏同学,今天我们就来说说 Vue-RouterReact-Router 中使用频率非常高的路由拦截鉴权。这也是我VueReact对比学习系列的第九篇文章啦。

路由拦截鉴权涉及的内容很多,笔者会分两篇文章来介绍。本文介绍的是vuereact系统的登录拦截。也就是登录了才能进入系统的某些页面,没登录的话需要重定向到登录页。这种形式的拦截鉴权基本上每个系统都会有这样的需求。

如果你对登录拦截这块很熟悉了,而且目前系统涉及到了更深层的角色权限,但是对系统角色权限设计和实现有疑惑不知道怎么做的,可以跳过本文直接去看笔者写的 Vue和React对比学习之路由角色权限,这篇文章进一步讲解了vuereact系统中是怎么具体实现角色权限控制。

好了,话不多说我们直接进入正题。

Vue-Router3 路由拦截鉴权

在vue2中我们使用的路由主要是Vue-Router3,所以我们先来看看Vue-Router3的用法

Vue-Router 内置了七个路由守卫,对于路由拦截相关需求,基本上不需要我们额外开发,在不同场景使用对应的守卫函数就能很好的完成。

beforeEach(to, from, next) // 全局前置守卫
beforeResolve(to, from, next) // 全局解析守卫
beforeEnter(to, from, next) // 路由守卫
beforeRouteEnter(to, from, next) // 组件内守卫(进入)
beforeRouteUpdate(to, from, next) // 组件内守卫(更新)
beforeRouteLeave(to, from, next) // 组件内守卫(离开)
afterEach(to, from) // 全局后置守卫

虽然 Vue-Router 给我们提供了这么多的守卫函数,但是在实际开发过程中,对于前端鉴权这块,我们基本上都会使用 beforeEach 全局前置守卫。

下面来看笔者的例子

定义 routes

首先我们定义好系统的路由,对于非首页,我们一般都会使用路由懒加载。

meta 里面可以定义我们需要的元数据,比如说是否需要登录、网页标题等等。

// router/routes.js

import Home from "../views/Home.vue"

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
    meta: {
      needLogin: false, // 不需要登录
      title: "首页"
    }
  },
  {
    path: "/about",
    name: "About",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"), // 路由懒加载
    meta: {
      needLogin: true, // 需要登录
      title: "关于"
    }
  },
  {
    path: "/login",
    name: "Login",
    component: () =>
      import(/* webpackChunkName: "login" */ "../views/Login.vue"), // 路由懒加载
    meta: {
      needLogin: false, // 不需要登录
      title: "登录"
    }
  },
];

export default routes;

实例化 Router

然后创建路由,vue2vue3创建路由的时候稍有区别,但是在路由鉴权那块是通用的。

// router/index.js

// vue2 写法
import VueRouter from "vue-router";
import routes from "./routes"

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

定义路由拦截鉴权逻辑

创建好路由后,我们可以来定义路由拦截的逻辑了,主要通过 beforeEach 全局前置守卫。因为只要页面发生跳转都会进入 beforeEach 全局前置守卫。

这里的核心逻辑就是判断前往的页面是否需要登录,需要登录就进一步判断当前系统是否有token,有就进入页面,没有就重定向到登录页。

// router/index.js

// vue2和vue3通用
router.beforeEach((to, from, next) => {
  // 如果需要登录
  if (to.meta.needLogin) {
    // 获取token
    const token = localStorage.getItem("token");

    // 如果有token 则直接放行
    if (token) {
      next();
    } else {
      // 否则去登录页
      next("/login");
    }
  } else {
    // 不需要登录则直接放行
    next();
  }
});

// 修改标题的工作可以放在全局后置守卫
router.afterEach((to, from) => {
  if (to.meta.title) {
    document.title = to.meta.title;
  }
});

export default router;

使用

创建完路由后需要在 main.js 导入使用

import router from "./router";

// vue2写法
new Vue({
  router,
  render: (h) => h(App),
}).$mount("#app");

我们来看看效果

d5c7450b-11b7-41a6-8e86-2a271ede96f9.gif

在没有 token(也就是没有登录的时候),去需要登录的页面是不可以进入的,会自动跳转到登录页。

本地设置好 token 模拟登录后,才能正常进入需要登录的页面。

Vue-Router4 路由拦截鉴权

vue3 中使用的路由是 Vue-Router4 版本,我们来看看相较 Vue-Router3 有哪些区别。

Vue-Router 内置了六个路由守卫,对于路由拦截相关需求,基本上不需要我们额外开发,在不同场景使用对应的守卫函数就能很好的完成。

beforeEach(to, from, next) // 全局前置守卫
beforeResolve(to, from, next) // 全局解析守卫
beforeEnter(to, from, next) // 路由守卫
onBeforeRouteUpdate(to, from, next) // 组件内守卫(更新)
onBeforeRouteLeave(to, from, next) // 组件内守卫(离开)
afterEach(to, from) // 全局后置守卫

虽然 Vue-Router 给我们提供了这么多的守卫函数,但是在实际开发过程中,对于前端鉴权这块,我们基本上都会使用 beforeEach 全局前置守卫。

下面来看笔者的例子

定义 routes

首先我们定义好系统的路由,对于非首页,我们一般都会使用路由懒加载。

meta 里面可以定义我们需要的元数据,比如说是否需要登录、网页标题等等。

// router/routes.js

import Home from "../views/Home.vue"

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
    meta: {
      needLogin: false, // 不需要登录
      title: "首页"
    }
  },
  {
    path: "/about",
    name: "About",
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"), // 路由懒加载
    meta: {
      needLogin: true, // 需要登录
      title: "关于"
    }
  },
  {
    path: "/login",
    name: "Login",
    component: () =>
      import(/* webpackChunkName: "login" */ "../views/Login.vue"), // 路由懒加载
    meta: {
      needLogin: false, // 不需要登录
      title: "登录"
    }
  },
];

export default routes;

实例化 Router

然后创建路由,vue2vue3创建路由的时候稍有区别,但是在路由鉴权那块是通用的。

// router/index.js

// vue3 写法
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes"

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

定义路由拦截鉴权逻辑

创建好路由后,我们可以来定义路由拦截的逻辑了,主要通过 beforeEach 全局前置守卫。因为只要页面发生跳转都会进入 beforeEach 全局前置守卫。

这里的核心逻辑就是判断前往的页面是否需要登录,需要登录就进一步判断当前系统是否有token,有就进入页面,没有就重定向到登录页。

// router/index.js

// vue2和vue3通用
router.beforeEach((to, from, next) => {
  // 如果需要登录
  if (to.meta.needLogin) {
    // 获取token
    const token = localStorage.getItem("token");

    // 如果有token 则直接放行
    if (token) {
      next();
    } else {
      // 否则去登录页
      next("/login");
    }
  } else {
    // 不需要登录则直接放行
    next();
  }
});

// 修改标题的工作可以放在全局后置守卫
router.afterEach((to, from) => {
  if (to.meta.title) {
    document.title = to.meta.title;
  }
});

export default router;

使用

创建完路由后需要在 main.js 导入使用

import router from "./router";

// vue3写法
const app = createApp(App);
app.use(router).mount("#app");

我们来看看效果

d5c7450b-11b7-41a6-8e86-2a271ede96f9.gif

在没有 token(也就是没有登录的时候),去需要登录的页面是不可以进入的,会自动跳转到登录页。

本地设置好 token 模拟登录后,才能正常进入需要登录的页面。

下面我们来看看React中的路由鉴权。

由于 React-Router4/5React-Router6 有较大的改动,所以这里我们分两个模块来讲。如果对 React-Router4/5React-Router6 区别还不熟悉的可以看看 React-Router6路由新特性(React-Router4/5和React-Router6对比总结) 一文。

React-Router4/5 路由拦截鉴权

我们先来看看 React-Router4/5 的路由拦截。

定义 routes

老样子,我们先定义项目的 routes。

// router/routes.js

import Home from "../views/Home";
import About from "../views/About";
import Login from "../views/Login";
import Child1 from "../views/Child1";
import Child2 from "../views/Child2";
import Error404 from "../views/Error404";

const routes = [
  {
    component: Home,
    path: "/home",
    meta: {
      title: "首页",
      needLogin: false,
    },
    routes: [
      {
        path: "/home/child1",
        component: Child1,
        meta: {
          title: "子页面1",
          needLogin: false,
        },
      },
      {
        path: "/home/child2",
        component: Child2,
        meta: {
          title: "子页面2",
          needLogin: true,
        },
      },
    ],
  },
  {
    path: "/about",
    component: About,
    meta: {
      title: "关于",
      needLogin: true,
    },
  },
  {
    path: "/login",
    component: Login,
    meta: {
      title: "登录",
      needLogin: false,
    },
  },
  // 放后面
  {
    path: "/",
    redirect: "/home/child1",
    exact: true,
  },
  // 放最后
  {
    path: "*",
    component: Error404,
  },
];

export default routes;

定义高阶组件 Auth

然后我们定义一个Auth高阶组件,用来处理权限相关逻辑。

// router/Auth.js

import { Route, Redirect } from "react-router-dom";

function Auth(props) {
  const {
    component: Component,
    path,
    meta,
    routes,
    redirect,
    exact,
    strict,
  } = props;

  // 设置网页标题
  if (meta && meta.title) {
    document.title = meta.title;
  }

  // 重定向
  if (redirect) {
    return <Redirect to={redirect} />;
  }

  // 判断是否需要登录
  if (meta && meta.needLogin) {
    const token = localStorage.getItem("token");
    // 没登录去登录页
    if (!token) {
      return <Redirect to="/login" />;
    }
  }

  return (
    <Route
      path={path}
      exact={exact}
      strict={strict}
      render={(props) => <Component {...props} routes={routes} />}
    ></Route>
  );
}

export default Auth;

根据 routes 结合 Auth 渲染路由

在根组件根据我们的routes配置来渲染Auth组件。

// app.jsx

import routes from "./router/routes";
import { Switch } from "react-router-dom";

export default function App() {
  return (
    <div className="app-wrapper">
      <Switch>
        {routes.map((route) => {
          return (
            // 路由鉴权
            <Auth key={route.path} {...route}></Auth>
          );
        })}
      </Switch>
    </div>
  );
}

由于 React-Router4/5 子路由需要定义在对应父组件里面,所以父组件我们也需要根据子routes渲染Auth组件。

// Home.js
import { Switch } from "react-router-dom";

export default function Home(props) {
  return (
    <div className="home-wrapper">
      <Switch>
        {props.routes.map((route) => {
          return (
            // 路由鉴权
            <Auth key={route.path} {...route}></Auth>
          );
        })}
      </Switch>
    </div>
  );
}

使用

最后,别忘记在入口文件使用 BrowserRouter 组件。

// index.jsx

import { BrowserRouter } from "react-router-dom";

ReactDOM.createRoot(document.getElementById("root")).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

我们来看看效果

d317e5af-26cc-487f-9f81-085ad5dab52f.gif

这样就达到了路由鉴权的效果,当我们本地没token的时候,根据配置进入/home/child2或者/about 的时候needLogin: true,所以会重定向到/login页面的。

当我们本地设置了 token 模拟登录后,就能正常进入这些需要登录权限的页面了。

React-Router6 路由拦截鉴权

React-Router6 相较 React-Router4/5 有了很大的改进,所以鉴权的实现相对来说会简单一点。

定义routes

老规矩,我们先定义项目的 routes

// router/routes.js

import Home from "../views/Home";
import About from "../views/About";
import Login from "../views/Login";
import Child1 from "../views/Child1";
import Child2 from "../views/Child2";
import Error404 from "../views/Error404";

import { Navigate } from "react-router-dom";
import Auth from "./Auth";

const routes = [
  {
    element: <Home></Home>,
    path: "/home",
    meta: {
      title: "首页",
      needLogin: false,
    },
    children: [
      {
        path: "/home/child1",
        element: <Child1></Child1>,
        meta: {
          title: "子页面1",
          needLogin: false,
        },
      },
      {
        path: "/home/child2",
        element: <Child2></Child2>,
        meta: {
          title: "子页面2",
          needLogin: true,
        },
      },
    ],
  },
  {
    path: "/about",
    element: <About></About>,
    meta: {
      title: "关于",
      needLogin: true,
    },
  },
  {
    path: "/login",
    element: <Login></Login>,
    meta: {
      title: "登录",
      needLogin: false,
    },
  },
  // 放后面
  {
    path: "/",
    redirect: "/home/child1",
  },
  // 放最后
  {
    path: "*",
    element: <Error404></Error404>,
  },
];

// HOC
const authLoad = (element, meta = {}) => {
  return <Auth meta={meta}>{element}</Auth>;
};

// 路由配置列表数据转换
export const transformRoutes = (routes) => {
  const list = [];
  routes.forEach((route) => {
    const obj = { ...route };
    if (obj.redirect) {
      obj.element = <Navigate to={obj.redirect} replace={true} />;
    }

    if (obj.element) {
      obj.element = authLoad(obj.element, obj.meta);
    }

    delete obj.redirect;
    delete obj.meta;

    if (obj.children) {
      obj.children = transformRoutes(obj.children);
    }
    list.push(obj);
  });
  return list;
};

export default routes;

定义高阶组件 Auth

然后我们定义一个Auth高阶组件,用来处理权限相关逻辑。

// router/Auth.js

import { Navigate } from "react-router-dom";

export default function Auth(props) {
  const { meta } = props;

  // 设置标题
  if (meta && meta.title) {
    document.title = meta.title;
  }

  const token = localStorage.getItem("token");

  // 权限校验,需要token但是没有token就重定向去登录页
  if (meta && meta.needLogin && !token) {
    return <Navigate to="/login" replace></Navigate>;
  }

  return <>{props.children}</>;
}

根据 routes 结合 useRoutes 渲染路由

React-Router6 相较 React-Router4/5 的重大优势可以就是可以通过 useRoutes 直接根据路由配置来渲染路由组件。

// app.jsx

import "./App.css";
import routes, { transformRoutes } from "./router/routes";
import { useRoutes } from "react-router-dom";

function App() {
  // 将路由配置 转换成 useRoutes 需要的结构
  const pages = useRoutes(transformRoutes(routes));

  return (
    <div className="app-wrapper">
      {pages}
    </div>
  );
}

export default App;

在父组件我们不需要再遍历渲染子组件了,直接使用useOutlet hook就可以了

// Home.js
import { useOutlet } from "react-router-dom";

export default function Home(props) {
  const outlet = useOutlet();
  return (
    <div className="home-wrapper">
      {outlet}
    </div>
  );
}

使用

别忘记了,在入口文件我们需要使用 BrowserRouter 组件。

// index.jsx

import { BrowserRouter } from "react-router-dom";

ReactDOM.createRoot(document.getElementById("root")).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

我们来看看效果

6ce3e308-b29d-4651-9ea0-bfa81fcea6ea.gif

这样就达到了路由鉴权的效果,当我们本地没token的时候,进入/home/child2或者/about 的时候会重定向到/login页面的。

当我们本地设置了 token 模拟登录后,就能正常进入这些需要登录权限的页面了。

总结

Vue 中,实现路由拦截鉴权的核心就是使用 vue-router 自身提供的 beforeEach 全局前置守卫。

React中,实现路由拦截鉴权的核心就是将每个路由组件使用高阶组件进行包裹,在这个高阶组件里面进行权限相关逻辑的判断。

系列文章

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)

Vue和React对比学习之组件传值(Vue2 12种、Vue3 9种、React 7种)

Vue和React对比学习之Style样式

Vue和React对比学习之Ref和Slot

Vue和React对比学习之Hooks

Vue和React对比学习之路由(Vue-Router、React-Router)

Vue和React对比学习之状态管理 (Vuex和Redux)

Vue和React对比学习之条件判断、循环、计算属性、属性监听

Vue和React对比学习之路由拦截鉴权

Vue和React对比学习之路由角色权限(页面、按钮权限控制)

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!