写在开头
2021年06月,小编写了一篇 "完整的Axios封装-单独API管理层、参数序列化、取消重复请求、Loading、状态码..." 的文章。
直到今天刚好过去两年半🤡,练习了两年半,今天终于来把当初缺的 TS
版本给补上。
正文
话不多说,直接上代码:
javascript
import axios from 'axios';
import type { AxiosRequestConfig, InternalAxiosRequestConfig, Canceler } from 'axios';
import { ElLoading, ElMessage } from 'element-plus';
import type { LoadingOptions } from 'element-plus';
import type { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
import { getTokenAUTH } from '@/utils/anth';
/** @name 自定义配置 **/
interface CustomOptions {
/** @name 是否开启loading层效果,默认为false **/
loading: boolean,
/** @name 是否开启取消重复请求,默认为 true **/
repeat_request_cancel: boolean,
/** @name 是否开启简洁的数据结构响应,默认为true **/
reduct_data_format: boolean,
/** @name 是否开启接口错误信息展示,默认为true **/
error_message_show: boolean,
/** @name 是否开启code不为0时的信息提示,默认为false **/
code_message_show: boolean,
}
const pendingMap: Map<string, Canceler> = new Map();
/** @name 记录loading单实例 **/
const loadingInstance: {
_target: LoadingInstance | null,
_count: number
} = {
_target: null,
_count: 0
};
/**
* @name 基于axios的二次封装
* @param { AxiosRequestConfig } axiosConfig axios配置
* @param { Partial<CustomOptions>?= } customOptions 自定义配置
* @param { LoadingOptions?= } loadingOptions element-plus的loading配置
*/
function myAxios(axiosConfig: AxiosRequestConfig, customOptions?: Partial<CustomOptions>, loadingOptions?: LoadingOptions) {
const service = axios.create({
baseURL: '', // 设置统一的请求前缀
timeout: 10000, // 设置统一的超时时长
});
// 自定义配置
let custom_options: CustomOptions = Object.assign({
loading: false,
repeat_request_cancel: true,
reduct_data_format: true,
error_message_show: true,
code_message_show: false,
}, customOptions);
// 请求拦截
service.interceptors.request.use(
config => {
removePending(config);
custom_options.repeat_request_cancel && addPending(config);
if (custom_options.loading) {
loadingInstance._count++;
if (loadingInstance._count === 1) {
loadingInstance._target = ElLoading.service(loadingOptions);
}
}
if (getTokenAUTH() && typeof window !== "undefined") {
config.headers.Authorization = getTokenAUTH();
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截
service.interceptors.response.use(
response => {
removePending(response.config);
custom_options.loading && closeLoading(custom_options);
if (custom_options.code_message_show && response.data && response.data.code !== 0) {
ElMessage({
type: 'error',
message: response.data.message
});
return Promise.reject(response.data);
}
return custom_options.reduct_data_format ? response.data : response;
},
error => {
error.config && removePending(error.config);
custom_options.loading && closeLoading(custom_options);
custom_options.error_message_show && httpErrorStatusHandle(error);
return Promise.reject(error);
}
);
return service(axiosConfig)
}
/** @name 关闭loading **/
function closeLoading(_options: CustomOptions) {
if (_options.loading && loadingInstance._count > 0) loadingInstance._count--;
if (loadingInstance._count === 0 && loadingInstance._target) {
loadingInstance._target.close();
loadingInstance._target = null;
}
}
/** @name 记录接口 **/
function addPending(config: InternalAxiosRequestConfig<any>) {
const pendingKey = getPendingKey(config);
config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, cancel);
}
});
}
/** @name 移除接口 **/
function removePending(config: InternalAxiosRequestConfig<any>) {
const pendingKey = getPendingKey(config);
if (pendingMap.has(pendingKey)) {
const cancel = pendingMap.get(pendingKey);
cancel && cancel(pendingKey);
pendingMap.delete(pendingKey);
}
}
/** @name 通过接口请求信息生成唯一的key **/
function getPendingKey(config: InternalAxiosRequestConfig<any>) {
let { url, method, params, data } = config;
if (typeof data === 'string') data = JSON.parse(data);
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}
/** @name 接口异常时,预订制message **/
function httpErrorStatusHandle(error: any) {
if (axios.isCancel(error)) return console.error('请求的重复请求:' + error.message);
let messageString = '';
if (error && error.response) {
switch (error.response.status) {
case 302: messageString = '接口重定向了!'; break;
case 400: messageString = '参数不正确!'; break;
case 401: messageString = '您未登录,或者登录已经超时,请先登录!'; break;
case 403: messageString = '您没有权限操作!'; break;
case 404: messageString = `请求地址出错: ${error.response.config.url}`; break;
case 408: messageString = '请求超时!'; break;
case 409: messageString = '系统已存在相同数据!'; break;
case 500: messageString = '服务器内部错误!'; break;
case 501: messageString = '服务未实现!'; break;
case 502: messageString = '网关错误!'; break;
case 503: messageString = '服务不可用!'; break;
case 504: messageString = '服务暂时无法访问,请稍后再试!'; break;
case 505: messageString = 'HTTP版本不受支持!'; break;
default: messageString = '异常问题,请联系管理员!'; break
}
}
if (error.message.includes('timeout')) messageString = '网络请求超时!';
if (error.message.includes('Network')) messageString = window.navigator.onLine ? '服务端异常!' : '您断网了!';
ElMessage({
type: 'error',
message: messageString
});
}
export default myAxios;
具体使用和原来的一致,并且现在有快捷提示,使用起来更加方便。
Em...整体来说应该不算难,就是补上了一些类型限制和JSDoc说明,具体的逻辑过程是没有改变的,可以自己瞅瞅,有疑问的地方欢迎评论区留言。😉
结尾
最后补一下关于 @/utils/auth
文件的内容,主要是获取 Token
的过程。
当初之所以没写这块内容,也是考虑到每个人、每个项目存储 Token
的方式可能都不一样,就干脆不写了。
而且,当初小编的方式比较简单,如下:
javascript
const TOKEN_KEY = '__TOKEN';
export function setTokenAUTH<T = any>(token: T) {
sessionStorage.setItem(TOKEN_KEY, JSON.stringify(token));
}
export function getTokenAUTH<T = any>(): T | null {
const value = sessionStorage.getItem(TOKEN_KEY);
if (value) {
const result: T = JSON.parse(value);
return result;
}
return null;
}
export function removeTokenAUTH() {
sessionStorage.removeItem(TOKEN_KEY);
}
是吧是吧🤡。
☃️🌲写这篇文章的时候刚好是圣诞节,圣诞节快乐⛄️🎄
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。