简介
哈喽大家好,我是苏苏同学,今天我们就来说说 Vue-Router
和 React-Router
中使用频率非常高的路由拦截鉴权。这也是我Vue
和React
对比学习系列的第九篇文章啦。
阅读本文前,笔者假设各位对 Vue-Router
和 React-Router
的使用都有了基本的了解,如果对 Vue-Router
或者 React-Router
还不熟悉的同学可以先看看笔者前面写的Vue和React对比学习之路由(Vue-Router、React-Router)
好了,话不多说我们直接进入正题。
Vue 路由拦截鉴权
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
然后创建路由,vue2
和vue3
创建路由的时候稍有区别,但是在路由鉴权那块是通用的。
// 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,
});
// 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";
// vue2写法
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
// vue3写法
const app = createApp(App);
app.use(router).mount("#app");
我们来看看效果
在没有 token
(也就是没有登录的时候),去需要登录的页面是不可以进入的,会自动跳转到登录页。登陆完后才能去需要登录的页面。
下面我们来看看React
中的路由鉴权。由于 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>
);
我们来看看效果
这样就达到了路由鉴权的效果,当我们本地没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
就可以了
// 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>
);
我们来看看效果
这样就达到了路由鉴权的效果,当我们本地没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对比学习之路由(Vue-Router、React-Router)
Vue和React对比学习之状态管理 (Vuex和Redux)
Vue和React对比学习之条件判断、循环、计算属性、属性监听
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!