Vue3 + Ts + Vite 封装一套企业级axiso全流程

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾f8e3cc1a0f694ac2b665ca2ad14c49d7.png 

从零搭建 Vue3 + VIte + Ts 项目 —— 并集成eslint 、prettier、stylelint、husky、lint-staged、pinia、axios、loding、动态路由…_彩色之外的博客-CSDN博客

Vue3 + Ts + Vite 封装一套企业级axiso全流程_第1张图片

   实现功能:

  1. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求

  2. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数

  3. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存

  4. 父页面单独取消当前请求、并发取消指定请求

  5. 父页面取消所有请求

  6. 更多功能根据你的需求自定制

目录

   第一 配置 vite.config.ts 基地址: 

  第二 配置环境变量:

  第三  配置ts类型

  第四 封装本地存储

  第五 封装axios: 

  第六 页面使用:


第一 配置 vite.config.ts 基地址: 

Vue3 + Ts + Vite 封装一套企业级axiso全流程_第2张图片

  第二 配置环境变量:

Vue3 + Ts + Vite 封装一套企业级axiso全流程_第3张图片

Vue3 + Ts + Vite 封装一套企业级axiso全流程_第4张图片

第三  配置ts类型

src/type/axiso.d.ts

/* eslint-disable */
import * as axios from 'axios';

// 扩展 axios 数据返回类型,可自行扩展
declare module 'axios' {
	export interface AxiosResponse {
		code: number;
		data: T;
		message: string;
		type?: string;
		/* 
		[key: string]: T; 这段代码是定义了一个索引签名,它表示可以使用任意字符串作为key,并且对应的值的类型是T。
		索引签名允许在对象中使用动态的属性,也就是说,在定义AxiosResponse接口时,除了预定义的code、data、message属性,还可以添
		加其他任意属性,且属性的值的类型是T。
	*/
		[key: string]: T;
	}
	export interface AxiosRequestConfig {
		retry?: number;
		retryDelay?: number;
		cache?: boolean;
		cacheTimestamp?: number;
		[key: string]: T;
	}
	export interface AxiosError {
		config: AxiosRequestConfig;
		code?: string;
		request?: any;
		response?: AxiosResponse;
		isAxiosError: boolean;
		retry?: number;
		retryDelay?: number;
		retryCount: number;
		cache?: boolean;
		cacheTimestamp?: number;
		[key: string]: T;
	}

	export interface CancelTokenSource {
		token: CancelToken;
		cancel: Canceler;
		isFinished?: boolean;
		[key: string]: T;
	}
}

  第四 封装本地存储

/**
 * window.localStorage 浏览器永久缓存
 * @method set 设置永久缓存
 * @method get 获取永久缓存
 * @method remove 移除永久缓存
 * @method clear 移除全部永久缓存
 */
export const Local = {
	// 设置永久缓存
	set(key: string, val: any) {
		window.localStorage.setItem(key, JSON.stringify(val));
	},
	// 获取永久缓存
	get(key: string) {
		let json = window.localStorage.getItem(key);
        // !null为true
		if (!json) return null;
		return JSON.parse(json);
	},
	// 移除永久缓存
	remove(key: string) {
		window.localStorage.removeItem(key);
	},
	// 移除全部永久缓存
	clear() {
		window.localStorage.clear();
	},
};

/**
 * window.sessionStorage 浏览器临时缓存
 * @method set 设置临时缓存
 * @method get 获取临时缓存
 * @method remove 移除临时缓存
 * @method clear 移除全部临时缓存
 */
export const Session = {
	// 设置临时缓存
	set(key: string, val: any) {
		window.sessionStorage.setItem(key, JSON.stringify(val));
	},
	// 获取临时缓存
	get(key: string) {
		let json = window.sessionStorage.getItem(key);
		if (!json) return null;
		return JSON.parse(json);
	},
	// 移除临时缓存
	remove(key: string) {
		window.sessionStorage.removeItem(key);
	},
	// 移除全部临时缓存
	clear() {
		window.sessionStorage.clear();
	},
};

   第五 封装axios: 

新建 \src\api 文件夹,里面有三个ts文件,request.ts 封装axios统一请求,requestMethod.ts   封装的是请求方法,api.ts 封装的是api接口,方便统一管理不至于api接口分散项目各处造成不易维护。

src\api\request.ts

import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, CancelTokenSource } from 'axios';
/* 
	1. 取消重复请求:完全相同的接口在上一个pending状态时,自动取消下一个请求 
	2. 请求失败自动重试: 接口请求后台异常时候, 自动重新发起多次请求, 直到达到所设次数 
	3. 请求接口数据缓存: 接口在设定时间内不会向后台获取数据, 而是直接拿本地缓存
	4. 父页面单独取消当前请求
	5. 父页面取消所有请求
*/

// 导入 element-plus 中的消息和弹框组件
import { ElMessage, ElMessageBox } from 'element-plus';
// 导入 storage.ts 中的 Session 对象
import { Session } from '/@/utils/storage';
// 导入 main.ts 中的 app 对象 用于Loading组件的显示和隐藏
import app from '/@/main';

// 创建 Axios 实例
const service: AxiosInstance = axios.create({
	baseURL: import.meta.env.VITE_API_URL, // 设置基础 URL
	timeout: 5000, // 设置超时时间
	headers: { 'Content-Type': 'application/json' }, // 设置请求头
});

// handlerRequest Start --------------------------------------------------------------------------

// cacheTimestamp用于判断是否存在相同缓存的请求
let cacheTimestampFlag = 0;
// requestKey用于缓存接口函数 判断是否存在相同的请求
let requestKey = '';
// 创建一个存储请求的Map对象
const pendingRequests: Map = new Map();
// 取消重复请求的方法
const cancelDuplicateRequest = (config: AxiosRequestConfig): void => {
	// 生成请求的唯一标识
	requestKey = `${config.method}-${config.url}`;

	// 如果已经存在该请求,则取消该请求
	if (pendingRequests.has(requestKey)) {
		const cancelToken = pendingRequests.get(requestKey);
		cancelToken?.cancel('进行中的重复请求被拦截,请您等待当前请求完成后再发起请求');
	}
	// 生成一个取消请求的标识
	const cancelToken = axios.CancelToken.source();
	// 将该请求保存到 pendingRequests 中
	pendingRequests.set(requestKey, cancelToken);
	// 设置取消请求的标识
	config.cancelToken = cancelToken.token;
	// 设置缓存时间
	if (config.cacheTimestamp) {
		cacheTimestampFlag = eval(`1000 * 60 * ${config.cacheTimestamp}`);
	}
	// 如果本地有缓存数据,直接返回缓存数据,不经过请求拦截
	/* 
	当Promise被拒绝并且这个拒绝状态没有被处理(即没有调用.catch()方法)时,	会在控制台打印出一个未捕获的Promise拒绝警告。
	如果你不希望在控制台看到这个警告,你需要确保每个Promise都有一个.catch()方法来处理拒绝状态
	*/
	if (config.cache)
		requestIsCache()
			.then(() => {})
			.catch(() => {});
};
// 缓存接口函数 - 注意发起请求判断是否存在相同url需要再组件中调用此函数,不会进过这里的请求拦截器
async function requestIsCache(): Promise {
	// 获取本地存储的所有键
	const keys = Object.keys(sessionStorage);
	if (requestKey) {
		// 停留时间 > 缓存时间阈值
		const isCache = Date.now() - Session.get(requestKey)?.cacheTimestamp > cacheTimestampFlag;
		// console.log('是否有key', keys.includes(requestKey));
		// console.log('停留时间', Date.now() - Session.get(requestKey)?.cacheTimestamp);
		// console.log('判断阈值', cacheTimestampFlag);

		// 如果包含 requestKey 并且 缓存未过期
		if (keys.includes(requestKey) && !isCache) {
			// 直接返回本地缓存数据
			const cacheData = Session.get(requestKey);
			return Promise.resolve(cacheData);
		} else {
			// 清除缓存
			Session.remove(requestKey);
			return Promise.reject('接口开启了catch,不存在缓存 或 缓存已过期');
		}
	}
}

// 封装提示函数 (token过期、重复登录)
const tipError = (value: string, title: string) => {
	ElMessageBox.alert(value, title, {
		confirmButtonText: title,
		type: 'warning',
	}).then(() => {
		Session.clear(); // 清除临时缓存
		// 清除cookie
		document.cookie.split(';').forEach(function (c) {
			document.cookie = c
				.replace(/^ +/, '')
				.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
		});
		window.location.reload(); // 刷新页面
	});
};

// 请求失败自动重试的方法
const retryFailedRequest = async (error: AxiosError): Promise => {
	const config = error;
	// 如果没有设置重试次数 或 已经达到最大重试次数,则直接返回错误
	if (!config || !config.retry || config.retryCount >= config.retry) {
		return Promise.reject(config);
	}
	// 设置重试次数关闭阈值
	config.retryCount = config.retryCount || 0;
	// 重试次数自增
	config.retryCount += 1;
	// 设置重试延时
	const delay = config.retryDelay || 1000;
	// 延时处理
	await new Promise((resolve) => {
		setTimeout(() => resolve(), delay);
	});
	// console.log('   ==>:', service(config));
	return service(config);
};
// handlerRequest End --------------------------------------------------------------------------

// Axios 的请求拦截器期望返回一个配置对象,而不是响应对象。如果你试图返回一个响应对象,Axios 将会抛出一个错误。
service.interceptors.request.use(
	async (config: AxiosRequestConfig) => {
		// 在发送请求之前做些什么?
		const token = Session.get('token');
		if (token) config.headers!['token'] = token; // 在请求头中添加 token
		// 取消重复请求
		cancelDuplicateRequest(config);

		app.config.globalProperties.$smallLoading.showLoading();
		return config;
	},
	async (error) => {
		// 对请求错误做些什么?
		app.config.globalProperties.$smallLoading.hideLoading();
		// 请求失败重试
		await retryFailedRequest(error);
		return Promise.reject(error);
	}
);

// 响应拦截器
service.interceptors.response.use(
	(response) => {
		// 对响应数据做点什么? 这里只返回成功响应数据!
		const { config, data } = response;
		// 给 pendingRequests 标记一个isFinished为true 请求完成的标识
		const responseKey = `${config.method}-${config.url}`;
		const request = pendingRequests.get(responseKey);
		if (request && request.token) {
			pendingRequests.set(responseKey, { ...request, isFinished: true });
		}

		app.config.globalProperties.$smallLoading.hideLoading();

		// 判断是否有缓存
		if (config.cache) {
			const cachedResponse = Session.get(responseKey);
			if (cachedResponse) {
				return cachedResponse;
			} else {
				// 接口有 cache 参数,且缓存不存在,则缓存接口数据,并插入当前时间戳
				data.cacheTimestamp = new Date().getTime();
				Session.set(responseKey, data);
				return data;
			}
		} else {
			return data;
		}
	},
	(error) => {
		// 对响应错误数据做点什么?这里只显示错误消息!
		app.config.globalProperties.$smallLoading.hideLoading();
		/* 
			axios.isCancel(error) 是 Axios 库中的一个方法,用于判断一个错误对象是否是由于请求取消导致的。
			当使用 axios.CancelToken 取消请求时,会抛出一个带有一个 message 属性的错误对象。
			axios.isCancel(error) 的作用就是判断这个错误对象的类型,如果是由请求取消导致的错误,则返回 true,否则返回 false。
		    console.log('打印cancelToken.cancel('xxx')传入来的值', error.message);
	    */
		if (axios.isCancel(error)) {
			// 只提示请求取消有主动填写的消息 如:cancelToken.cancel('xxx')
			if (error.message !== 'canceled') ElMessage.error(requestKey + '  ' + error.message);
		} else {
			// 响应失败重试
			retryFailedRequest(error);
			// 不是由请求取消导致的错误
			let errorMessage; // 错误提示变量
			let statusData = error.response?.data; // 错误data数据
			const describeForNameMap = [
				[
					() => error.message.indexOf('timeout') !== -1,
					() => (errorMessage = '网络超时 '),
				],
				[() => error.message === 'Network Error', () => (errorMessage = '网络连接错误 ')],
				[
					() => statusData?.code === 100010021,
					() => {
						// 100010021 重复登录
						errorMessage = statusData.message;
						tipError(errorMessage, '重复登录');
					},
				],
				[
					() => statusData?.code === 100010011,
					() => {
						// 100010011 token过期
						errorMessage = statusData.message;
						tipError(errorMessage, '登录过期');
					},
				],
				// 否则 显示错误消息,这里要根据后台返回的数据结构来定
				[() => statusData, () => (errorMessage = statusData.message)],
			];
			// 获取符合条件的子数组
			const getDescribe = describeForNameMap.find((item) => item[0]());
			// 执行子数组中的函数
			getDescribe && getDescribe[1]();
			ElMessage.error(errorMessage); // 显示错误消息
		}
	}
);
// 取消全部请求的方法
export const cancelAllRequest = (): void => {
	// 创建一个标记 是否取消成功,初始值为false
	let hasCancelled = false;

	// 遍历所有待处理的请求
	pendingRequests.forEach((value) => {
		// 如果请求还没有完成
		if (!value.isFinished) {
			// 取消请求
			value.cancel();
			// 将标记设为true
			hasCancelled = true;
		}
	});

	// 清空待处理请求的集合
	pendingRequests.clear();

	// 至少取消了一个请求,显示提示,防止都是成功请求点击取消按钮时也提示
	if (hasCancelled) {
		ElMessage.success('成功取消全部请求');
	}
};

// 取消当前请求的方法
export const cancelCurrentRequest = (payloadCurrentKey: string = requestKey): void => {
	// 遍历所有待处理的请求
	pendingRequests.forEach((value, key) => {
		if (key === payloadCurrentKey) {
			value.cancel();
			pendingRequests.delete(key);
			ElMessage.success('成功取消当前请求');
		}
	});
};

// 导出 service将其命名为axios , requestIsCache 用于判断是否有缓存
export { service as axios, requestIsCache };

src\api\requestMethod.ts

import { axios } from './request';
// post使用data接受参数,get使用params接受参数
// 如果是post请求,但是参数是在url上的,那么就要使用params接受参数,否则使用data接受参数
// put 也相当与post请求,如果报参数错误,就是接受参数的请求体错了post/put用data,get请求用params
type method = 'GET' | 'POST' | 'PUT' | 'DELETE';
// 规定缓存时间戳的类型只能 1 - 5 分钟
type cacheTimestamp = 1 | 2 | 3 | 4 | 5;

/**
 * @name request 配置
 * @param { string } - method 请求方法
 * @param { string } - url   请求地址
 * @param { object } - data/params 请求参数
 * @param { number } - retry  重试次数
 * @param { number } - retryDelay 重试延迟
 * @param { boolean } -  cache 是否缓存
 * @param { number }  - cacheTimestamp 缓存过期 1-5分钟
 * @createDate 2023/08/09 13:12:08
 * @lastFixDate 2023/08/09 13:12:08
 */
interface requestConfig {
	method: method;
	url: string;
	data?: object;
	params?: object;
	retry?: number;
	retryDelay?: number;
	cache?: boolean;
	cacheTimestamp?: cacheTimestamp;
}

function request({
	method = 'GET',
	url,
	data = {},
	params = {},
	retry,
	retryDelay,
	cache,
	cacheTimestamp = 1,
}: requestConfig) {
	return axios({
		method,
		url,
		data,
		params,
		retry,
		retryDelay,
		cache,
		cacheTimestamp,
	});
}

export default request;

src\api/auth-manage/menu.ts

// 导入axios中的AxiosResponse的泛型接口
import { AxiosResponse } from 'axios';
// 导入封装的axios请求方法
import request from '/@/utils/requestMethod';

// 获取菜单树接口: 不包含菜单中的按钮
export const getMenuTree = (): Promise> =>
	request({
		method: 'POST',
		url: '/menu/queryMenuTree',
		cache: true,
		cacheTimestamp: 1,
	});

第六 页面使用:




全文结束,所有代码都在文中,最上面的链接中也有原项目

7730e2bd39d64179909767e1967da702.jpeg

 _______________________________  期待再见  _______________________________ 

你可能感兴趣的:(vue.js,前端,javascript,vite,ts)