当前位置: 首页 > article >正文

React封装登录逻辑

在写React项目的时候在登录这块对于用户身份失效自动跳到登录页这块让我头大,问题在于:知道请求为401的时候才会跳到登录页让用户身份认证,因为不可能在每个请求都要判断一下是否为401于是我们就会在封装axios的地方单独判断是否为401,此时问题就出来了如何在请求判断为401时通知组件更新,于是我就通过redux来通知因为我们知道redux既可以在组件中使用也可以在js文件中使用,废话不多说下面就是我的代码,点击我查看完整代码

第一步

判断是否请求为401,当请求为401时移除已存在的token并且使用redux的action更新store

http.request.js

import axios from "axios";
import {store} from "@/redux/store.js";
import {authorizeAction} from "@/redux/feature/authorize.js";
import {getToken, getTokenName, removeToken} from "@/lib/toolkit/local.storage.js";

const URL = import.meta.env.VITE_REACT_APP_PATH

// 创建 axios 请求实例
const serviceAxios = axios.create({
	baseURL:URL, // 基础请求地址
	timeout: 10000, // 请求超时设置
	withCredentials: false, // 跨域请求是否需要携带 cookie
});

// 创建请求拦截
serviceAxios.interceptors.request.use(
	(config) => {
		config.headers = {'Content-Type': 'application/json',...config.headers,[getTokenName()]:getToken()};
		return config;
	},
	(error) => {
		return Promise.reject(error);
	}
);


// 创建响应拦截
serviceAxios.interceptors.response.use(
	(res) => {
		if (res.data.code === 401) {
			//移除之前的token
			removeToken()
			store.dispatch(authorizeAction())
		}
		return res.data;
	},
	(error) => {
		let msg = "网络异常问题,请联系管理员!";
		if (error && error.response) {
			switch (error.response.status) {
				case 302:
					msg = "接口重定向了!";
					break;
				case 400:
					msg = "参数不正确!";
					break;
				case 401:
					msg = "您未登录,或者登录已经超时,请先登录!";
					break;
				case 403:
					msg = "您没有权限操作!";
					break;
				case 404:
					msg = `请求地址出错: ${error.response.config.url}`;
					break;
				case 408:
					msg = "请求超时!";
					break;
				case 409:
					msg = "系统已存在相同数据!";
					break;
				case 500:
					msg = "服务器内部错误!";
					break;
				case 501:
					msg = "服务未实现!";
					break;
				case 502:
					msg = "网关错误!";
					break;
				case 503:
					msg = "服务不可用!";
					break;
				case 504:
					msg = "服务暂时无法访问,请稍后再试!";
					break;
				case 505:
					msg = "HTTP 版本不受支持!";
					break;
				default:
					msg = "异常问题,请联系管理员!";
					break;
			}
		}
		return Promise.reject(msg);
	}
);

const request = {
	post:(url,data = {}) => {
		return serviceAxios({
			url: url,
			method: "post",
			data: data,
			headers: {
				"Content-Type": "application/json"
			}
		})
	},
	get:(url,params = {})=>{
		return serviceAxios({
			url: url,
			method: "get",
			params: params,
			headers: {
				"Content-Type": "application/json"
			}
		})
	}
}



export {URL}

export default request;

store.js: devTools用于浏览器插件,middleware:这是一个配置选项,默认情况下,Redux Toolkit 会检查每个动作和状态是否可序列化(即能被 JSON.stringify 处理),以确保 Redux 状态树的一致性和可预测性。在某些情况下(如使用某些类型的非序列化数据),可能需要关闭此检查

import {configureStore} from "@reduxjs/toolkit";
import {composeWithDevTools} from "@redux-devtools/extension";
import {authorizeReducer} from "@/redux/feature/authorize.js";

//存储状态
export const store = configureStore({
	reducer:{
		authorize:authorizeReducer
	},
	devTools:composeWithDevTools(),
	middleware : (getDefaultMiddleware) => {
		return getDefaultMiddleware({
			serializableCheck: false
		})
	}
});

authorize.js

import {isBlank} from "@/lib/toolkit/util.js";
import {generateSlice} from "@/lib/toolkit/redux.util.js";
import {getToken} from "@/lib/toolkit/local.storage.js";

const AUTHORIZE_SUCCESS = true;
const AUTHORIZE_FAIL = false;

/**
 *  用于监控TOKEN失效的state
 *  false 授权异常 true 授权正常
 *  TODO BUG 当用户是登录状态退出浏览器在登录会先跳转到首页再跳转到主页
 */
const authorizeProcessor = generateSlice(getRandomId(), AUTHORIZE_FAIL, {
    authorizeAction() {
        return isBlank(getToken()) ? AUTHORIZE_FAIL : AUTHORIZE_SUCCESS
    },
});
const authorizeProcessor = createSlice({
		name:'login',
		initialState:AUTHORIZE_FAIL,
		reducers:{
            authorizeAction: ()=>{
                return isBlank(getToken()) ? AUTHORIZE_FAIL : AUTHORIZE_SUCCESS
            }
        }
	})

export const authorizeReducer = authorizeProcessor.reducer
export const {authorizeAction} = authorizeProcessor.actions
export {AUTHORIZE_FAIL,AUTHORIZE_SUCCESS}

第二步

通知组件更新:封装HOOK放在顶层组件中,

main.jsx

import '@/index.css'
import 'virtual:uno.css'
import ReactDOM from 'react-dom/client';
import {RouterProvider} from "react-router-dom";
import {Suspense} from "react";
import {Loading} from "antd-mobile";
import {Provider} from "react-redux";
import router from "@/router/index.jsx";
import {store} from "@/redux/store.js";

//渲染
ReactDOM.createRoot(document.getElementById('root')).render(
    <Suspense fallback={<Loading/>} >
        <Provider store={store}>
            <RouterProvider router={router}/>
        </Provider>
    </Suspense>
)

App.jsx

import {useEffect} from "react";
import {Outlet, useNavigate} from "react-router-dom";
import {AUTH_PATH, HOME_PATH} from "@/router/index.jsx";
import {useToken} from "@/hook/useToken.jsx";


export default function App() {
    const navigate = useNavigate();
    const {isLogin} = useToken();

    useEffect(() => {
        if (isLogin) {
            navigate(HOME_PATH)
            return
        }
        navigate(AUTH_PATH)
    },[isLogin])
    return (
        <div className='w-full h-full'>
            <Outlet/>
        </div>
    )
}

useToken.jsx:当redux更新了此hook就会被触发并且其中的isLogin会重新计算相当于响应式数据,至于为什么要增加!isBlank(getToken()),就在于每次关闭重新打开我们的网站redux都会初始化,导致用户可能是有token也会被重定向到登录页,所以判断的时候还需要加上token是否存在

import {useDispatch, useSelector} from "react-redux";
import {getToken, removeToken, setToken} from "@/lib/toolkit/local.storage.js";
import {isBlank} from "@/lib/toolkit/util.js";
import {authorizeAction} from "@/redux/feature/authorize.js";

export const useToken = () => {
    const authorize = useSelector(state => state.authorize);
    const dispatch = useDispatch();

    return {
        isLogin: authorize || !isBlank(getToken()),
        token: getToken(),
        logout: () => {
            removeToken();
            dispatch(authorizeAction())
        },
        login: (token) => {
            setToken(token)
            dispatch(authorizeAction())
        }
    }
}

以上的代码就解决开头提出的问题,但是其中有很多工具类啥的代码不完整,点击我查看完整代码

工具类

**local.storage.js:isBlank为工具类的一个方法,其中的const TOKEN_NAME = import.meta.env.VITE_REACT_APP_TOKEN_NAME**语法是来自于dotenv-cli这个依赖包的可以百度自行查看用法,在这里就是获取token存在浏览器的KEY

import {isBlank} from "@/lib/toolkit/util.js";

const TOKEN_NAME = import.meta.env.VITE_REACT_APP_TOKEN_NAME

const getTokenName = () => {
    if (isBlank(TOKEN_NAME)) {
        throw new Error('TOKEN_NAME is null or undefined')
    }
    return TOKEN_NAME
}

const getToken = () => {
    return localStorage.getItem(getTokenName()) || ''
}

const setToken = (token) => {
    localStorage.setItem(getTokenName(), token)
}

const removeToken = () => {
    localStorage.removeItem(getTokenName())
}

const get = (key) => {
    return localStorage.getItem(key);
}

const set = (key,value) => {
    localStorage.setItem(key,value)
}

const remove = (key) => {
    localStorage.removeItem(key)
}

export {getTokenName, getToken, setToken, removeToken, get, set, remove}

因为这只是我自己想的如果有更好的解决方法,愿意请教。谢谢


http://www.kler.cn/news/323924.html

相关文章:

  • python的逻辑控制
  • JAVA打造全球商品集散地国际版多商户商城系统小程序源码
  • 1. go 环境与命令
  • python 获取当前git的repo地址
  • electron 设置界面右下角打开
  • 浅谈java异常[Exception]
  • JAVA红娘婚恋相亲交友系统源码全面解析
  • python 实现gradient boosting regressor梯度增强回归器算法
  • 车间调度问题数学建模与CPLEX优化
  • conda安装包离线安装环境
  • SO-ELM预测 | MATLAB实现SO-ELM蛇群算法优化极限学习机多输入单输出
  • Elasticsearch导出导入数据
  • GraphQL规范
  • C++动态内存管理
  • 基于大数据的亚健康人群数据分析及可视化系统
  • GEE 教程:如何在谷歌地球引擎中使用克里金插值?
  • ArcGIS Pro高级地图可视化—双变量符号地图
  • 极品飞车14热力追踪原始版高清重制版MOD分享
  • QT开发:深入详解Qt 核心类QTimer的概念及应用
  • Linux网络之UDP与TCP协议详解
  • wpf在图上画矩形,矩形可拖动、大小可调节,使用装饰器Adorner调整矩形大小,限制拖动和调节范围
  • Go语言流程控制
  • “AI+Security”系列第3期(四):360安全大模型业务实践
  • 一文上手Kafka【中】
  • 叉车高位显示器无线摄影,安装更加便捷!
  • 从“纸面算力”到“好用算力”,超聚变打通AI+“最后一公里”
  • RabbitMQ高级特性-重试机制
  • 备考中考的制胜法宝 —— 全国历年中考真题试卷大全
  • 【C++笔记】初始模版和STL简介
  • Python项目周报