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}

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

React Hooks 是 React 16.8 的新增特性,它可以让我们在不编写类组件的情况下,使用 state 和其他 React 特性。而封装 Hooks 则是将一些常用的逻辑抽象出来,以自定义 Hooks 的形式提供给其他组件使用。封装 Hooks 可以提高代码的复用性和可维护性。 封装 Hooks 的步骤大致如下: 1. 确定封装逻辑,将其抽象为一个自定义 Hook 函数。 2. 在 Hook 函数中使用 React Hooks API,如 useState、useEffect 等。 3. 将 Hook 函数暴露出去,供其他组件使用。 下面是一个简单的示例,封装了一个 useFetch 自定义 Hook,用于获取数据: ``` import { useState, useEffect } from 'react'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchData() { try { const response = await fetch(url); const json = await response.json(); setData(json); } catch (error) { setError(error); } finally { setLoading(false); } } fetchData(); }, [url]); return { data, loading, error }; } export default useFetch; ``` 这个 useFetch Hook 封装了一个异步获取数据的逻辑。其他组件可以通过调用 useFetch 获取数据并进行渲染: ``` import React from 'react'; import useFetch from './useFetch'; function MyComponent() { const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1'); if (loading) { return <div>Loading...</div>; } if (error) { return <div>Error: {error.message}</div>; } return ( <div> <h1>{data.title}</h1> <p>{data.body}</p> </div> ); } export default MyComponent; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值