React路由守卫权限管理

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

前言

路由守卫是指在路由跳转前、跳转后做一些动作所触发的钩子函数,在后台管理系统中涉及到权限控制相关的逻辑时经常会看见,在实现路由跳转真实动作前会先校验该登录用户是否有权限,或者是token是否过期才会通过,反之就会执行其他操作,例如返回首页或登录页。

那么如何通过react-router来实现项目中的路由守卫呢?一共有两种方案:

  • 通过公共高阶组件拦截;

  • 在项目根目录判断拦截;

封装组件

先说第一点,我们可以封装一个高阶组件,将所有渲染真实页面的路由组件传入该高阶组件,在高阶组件中判断权限逻辑,在react-router中可以使用Route组件的render属性或函数式组件来实现路由守卫。

使用render属性时,可以传入一个函数,根据需要渲染不同的组件或页面,在这个函数中实现路由守卫的具体逻辑,例如检查用户是否登录,根据用户角色判断是否拥有权限访问该页面等。如果不满足条件,可以返回一个权限提示并执行重定向,否则可以渲染目标组件或页面。

根据上述思路我们可以封装一个这样的RouteComponent组件,代码如下:

tsx 复制代码
import { Route, Redirect } from 'react-router-dom';

const RouteComponent = (props) => {
  const { component: Component, isAuth, ...rest } = props;
  return (
    <Route
      {...rest}
      render={(props) =>
        isAuth ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{
              pathname: '/login',
              state: { from: props.location },
            }}
          />
        )
      }
    />
  );
};

在上面的示例中,RouteComponent组件接收三个参数:

  • component,代表需要渲染的目标组件;

  • isAuth,代表是否有权限访问该页面;

  • rest,代表其他参数,传入react-routerRoute组件中,例如路由路径;

函数式组件hook

如果使用函数式组件hooks写法,可以把路由守卫的判断逻辑写在项目根文件中,通常为App.jsx中,在useEffect hook中,如果不满足权限条件,则通过history.push来手动重定向,代码块如下:

tsx 复制代码
import { useEffect } from 'react';
import { useHistory, useRoutes, Router } from 'react-router-dom';
import routes from './routes';

const App = () => {
  const history = useHistory();
  const isAuth = true;

  useEffect(() => {
    if (!isAuth) {
      history.push('/login');
    }
  }, [isAuth, history]);

  return <Router>{useRoutes(routes)}</Router>;
};

这里使用了react-router V6useRoutes钩子快速初始化路由列表,在useEffect中,如果用户未登录,就会走history.push方法将页面重定向到登录页面。当然,我们也可以更加优雅一点,根据实际业务场景封装出一个权限状态获取的useAuth hook

tsx 复制代码
import { useState, useEffect } from 'react';
import { getUserAuth } from '@/service';

const useAuth = () => {
  const [auth, setAuth] = useState({
    isLogin: false,
    superAdmin: false,
    userName: '',
  });

  useEffect(() => {
    getUserAuth();
  }, []);

  const getUserAuth = async () => {
    const res = await getUserAuth();
    if (res?.success && res?.data) {
      setAuth(res.data);
    }
  };

  return auth;
};

上述伪代码块中,在useEffect阶段调用了getUserAuth方法来请求服务器,获取当前用户的权限信息,将信息保存在useAuth的状态中,返回出去,对应上面的案例,就可以改造成这样:

tsx 复制代码
import { useEffect } from 'react';
import { useHistory, useRoutes, Router } from 'react-router-dom';
import routes from './routes';
import useAuth from './useAuth';

const App = () => {
  const history = useHistory();
  const { isLogin } = useAuth();

  useEffect(() => {
    if (!isLogin) {
      history.push('/login');
    }
  }, [isLogin, history]);

  return <Router>{useRoutes(routes)}</Router>;
};

当然,这个useAuth只是简单版本,可以通过具体的业务逻辑来改造,比如某个页面只有超管才能访问、某个页面必须要登录才能访问等等,把所有路由权限相关的逻辑都集成在useAuth中,就像这样:

tsx 复制代码
import { useState, useEffect } from 'react';
import { getUserStatus, getUserAuthCodeList } from '@/service';

const useAuth = () => {
  const [auth, setAuth] = useState({
    isLogin: false,
    superAdmin: false,
    userName: '',
  });
  const [pageAuth, setPageAuth] = useState(false);

  useEffect(() => {
    getUserStatus();
    getUserAuthCodeList();
  }, []);

  /**
   * @description: 获取用户状态
   */
  const getUserStatus = async () => {
    const res = await getUserStatus();
    if (res?.success && res?.data) {
      setAuth(res.data);
    }
  };

  /**
   * @description: 获取用户的页面映射权限表,通过当前页面来判断是否有单页面权限
   */
  const getUserAuthCodeList = async () => {
    const res = await getUserAuthCodeList();
    if (res?.success && res?.data) {
      const pathname = location.pathname;
      if (res.data[pathname]) {
        setPageAuth(true);
      }
    }
  };

  return {
    auth,
    pageAuth,
  };
};

这样useAuth hook就复杂了起来,需要同时满足用户已登录并且有该页面的权限才能访问,App.jsx页面部分逻辑就变成了这样:

tsx 复制代码
import { useEffect } from 'react';
import { useHistory, useRoutes, Router } from 'react-router-dom';
import { message } from 'antd';
import routes from './routes';
import useAuth from './useAuth';

const App = () => {
  const history = useHistory();
  const { auth, pageAuth } = useAuth();

  useEffect(() => {
    if (!auth.isLogin) {
      history.push('/login');
    }
    if (!pageAuth) {
      history.replace('/');
      message.error('当前页面没有权限');
    }
  }, [auth.isLogin, history]);

  return <Router>{useRoutes(routes)}</Router>;
};

当用户未登录,则跳转到登录页,如果当前页面没有权限,则返回到系统首页并给出错误message提示。

如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

相关推荐
子兮曰19 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰20 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万20 小时前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝20 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋20 小时前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁21 小时前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄1 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师1 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端