前言
最近写页面的时候把页面切成了多个组件,但是每个组件都需要一份字典的数据,如果用props传入的话就太过麻烦麻烦,想来想去还是对请求做一个缓存。
缓存的实现
我们首先要实现的是发起请求,如果响应成功后,接下来响应的接口都直接返回响应的数据。 这里我们可以用一个对象或者map将数据存起来,key的话可以用url加上请求数据生成。可以在axios请求里获取到这块数据。
js
export class CacheManager {
private cacheMap: Map<string, any> = new Map();
/**
* 根据请求生成key
* @param config axios请求
*/
public getCacheKey(config: AxiosRequestConfig): string {
if (config.params) {
return [config.method, config.url, ...Object.keys(config.params), ...Object.values(config.data)].join('&');
} else if (config.data) {
return [config.method, config.url, ...Object.keys(config.data), ...Object.values(config.data)].join('&');
}
return [config.method, config.url].join('&');
}
/**
* 获取缓存
* @param config axios请求
*/
public get<T = any>(config: AxiosRequestConfig) {
const key = this.getCacheKey(config);
const cache = this.cacheMap.get(key);
if (cache) {
return cache;
}
return;
}
/**
* 设置缓存
* @param config axios请求
* @param res 接口数据
*/
public set(config: AxiosRequestConfig, res: any) {
const key = this.getCacheKey(config);
this.cacheMap.set(key, res)
}
}
但是这样会会带来一个问题,如果我们在上一个接口还没响应的时候就发出第二个请求,这样我们的缓存就失去了左右,所以我们要给请求加上一个状态,完成和进行中。这样还会带来一个问题,如果把发起的请求暂停并且存储起来,等到响应后再发送过去呢?其实这个问题很好解决,接口的请求在前端无非是一个异步,一个异步只有两个状态,那我们只需要把这两个状态存起来就好,等到响应后根据接口放回对应的rosolve或reject。
所以我们这里可以这么改
js
import type { AxiosRequestConfig } from 'axios';
import type { ResponseResult } from './http.interface';
export type CacheStatus = 'pending' | 'complete';
export type CacheCallBackType = 'success' | 'error';
export interface Cache {
status: CacheStatus;
res?: ResponseResult; // 这里是自己定义的后端返回格式,可以自己修改或者any
callback: {
resolve: (data: any) => void;
reject: (error: any) => void;
}[];
}
export class CacheManager {
private cacheMap: Map<string, Cache> = new Map();
/**
* 根据请求生成key
* @param config axios请求
*/
public getCacheKey(config: AxiosRequestConfig): string {
if (config.params) {
return [config.method, config.url, ...Object.keys(config.params), ...Object.values(config.data)].join('&');
} else if (config.data) {
return [config.method, config.url, ...Object.keys(config.data), ...Object.values(config.data)].join('&');
}
return [config.method, config.url].join('&');
}
/**
* 获取缓存
* @param config axios请求
*/
public get<T = any>(config: AxiosRequestConfig): Promise<ResponseResult<T>> | undefined {
const key = this.getCacheKey(config);
const cache = this.cacheMap.get(key);
if (cache) {
if (cache.status === 'complete') {
return Promise.resolve(cache.res as ResponseResult<T>);
}
if (cache.status === 'pending') {
return new Promise((resolve, reject) => {
cache.callback.push({
resolve,
reject,
});
});
}
}
this.cacheMap.set(key, {
status: 'pending',
callback: [],
});
}
/**
* 设置缓存
* @param config axios请求
* @param res 接口数据
* @param cacheCallBackType Promise回调类型
*/
public set(config: AxiosRequestConfig, res: any, cacheCallBackType: CacheCallBackType) {
const key = this.getCacheKey(config);
const cache = this.cacheMap.get(key);
if (cache) {
if (cacheCallBackType === 'success') {
cache.res = res;
cache.status = 'complete';
for (let i = 0; i < cache.callback.length; i++) {
cache.callback[i].resolve(res);
cache.callback.splice(i, 1);
i--;
}
} else if (cacheCallBackType === 'error') {
for (let i = 0; i < cache.callback.length; i++) {
cache.callback[i].reject(res);
cache.callback.splice(i, 1);
i--;
}
this.cacheMap.delete(key);
}
}
}
}
封装Axios
封装Axios其实网上有很多文章了,我这里主要是借鉴了vben的封装,不是很难,就是把拦截器抽离出来而已。不同的是,我们都知道Axios会默认封装一层,接口响应的数据一般被放在了res.data里,但是我一般用不到,所以我把这一层直接去掉了。
直接贴上完整的代码
js
import axios, {
AxiosError,
type AxiosInstance,
type AxiosResponse,
type InternalAxiosRequestConfig,
} from 'axios';
import { AxiosCanceler } from './AxiosCanceler';
import type { AxiosCreateOption, RequestOptions, ResponseResult } from './http.interface';
import { CacheManager } from './CacheManger';
/**
* @description 封装axios 调用getInstance获取实例
*/
export class HttpRequest {
private axiosInstance: AxiosInstance;
private createOptions: AxiosCreateOption;
private axiosCanceler: AxiosCanceler = new AxiosCanceler();
private cacheManager: CacheManager = new CacheManager();
constructor(createOptions: AxiosCreateOption) {
this.axiosInstance = axios.create(createOptions);
this.createOptions = createOptions;
this.setInterceports();
}
/**
* @description 设置拦截器和取消请求
*/
private setInterceports(): void {
this.axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig & RequestOptions) => {
// 判断是否开启重复请求
let ignoreRepeatRequests = this.createOptions.ignoreRepeatRequests;
ignoreRepeatRequests =
config.ignoreRepeatRequests !== undefined
? config.ignoreRepeatRequests
: ignoreRepeatRequests;
ignoreRepeatRequests && this.axiosCanceler.addPending(config);
if (this.createOptions.interceptors?.onRequest) {
config = this.createOptions.interceptors.onRequest(config);
}
return config;
},
this.createOptions.interceptors?.onRequestError
);
this.axiosInstance.interceptors.response.use((res: AxiosResponse<ResponseResult>) => {
this.axiosCanceler.removePending(res.config);
if (this.createOptions.interceptors?.onResponse) {
res = this.createOptions.interceptors.onResponse(res);
}
return res;
}, this.createOptions.interceptors?.onResponseError);
}
/**
* @description 移除所有请求
*/
public allRequestCanceler(): void {
this.axiosCanceler.removeAllPending();
}
/**
* @param config axios请求数据
*/
public request<T = any>(config: RequestOptions): Promise<ResponseResult<T>> {
return new Promise(async (resolve, reject) => {
// 判断是否开启缓存
if (config.cache) {
const cacheData = this.cacheManager.get<T>(config);
// 不等于undefined意味着已经有正在pending状态了
if (cacheData !== undefined) {
try {
return resolve(await cacheData);
} catch (err) {
return reject(err);
}
}
}
this.axiosInstance
.request(config)
.then((res: AxiosResponse<ResponseResult>) => {
config.cache && this.cacheManager.set(config, res.data, 'success');
resolve(res.data as ResponseResult<T>);
})
.catch((err: AxiosError<ResponseResult>) => {
if (this.createOptions.interceptors?.onErrorCatch) {
const newErr = this.createOptions.interceptors?.onErrorCatch(err);
config.cache && this.cacheManager.set(config, newErr, 'error');
return reject(newErr);
}
config.cache && this.cacheManager.set(config, err, 'error');
reject(err);
});
});
}
public get<T = any>(
url: string,
config?: RequestOptions
): Promise<ResponseResult<T> | AxiosResponse<ResponseResult<T>>> {
return this.request<T>({ ...config, url, method: 'get' });
}
public post<T = any>(
url: string,
data?: any,
config?: RequestOptions
): Promise<ResponseResult<T> | AxiosResponse<ResponseResult<T>>> {
return this.request<T>({ ...config, url, data, method: 'post' });
}
public put<T = any>(
url: string,
data?: any,
config?: RequestOptions
): Promise<ResponseResult<T> | AxiosResponse<ResponseResult<T>>> {
return this.request<T>({ ...config, url, data, method: 'put' });
}
public delete<T = any>(
url: string,
config?: RequestOptions
): Promise<ResponseResult<T> | AxiosResponse<ResponseResult<T>>> {
return this.request<T>({ ...config, url, method: 'delete' });
}
public upload<T = any>(
url: string,
data?: any,
config?: RequestOptions
): Promise<ResponseResult<T> | AxiosResponse<ResponseResult<T>>> {
return this.request<T>({
...config,
url,
data,
method: 'post',
headers: { 'Content-Type': 'multipart/form-data', ...config?.headers },
});
}
}
这里是声明的类型
js
import type { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
/**
* @interface 创建实例参数
*/
export interface AxiosCreateOption extends AxiosRequestConfig {
interceptors?: Interceptors;
/**
* @description 是否开启重复请求
*/
ignoreRepeatRequests?: boolean;
}
/**
* @interface 拦截器
*/
export interface Interceptors {
/**
* @description 请求拦截
*/
onRequest?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
/**
* @description 响应拦截
*/
onResponse?: (res: AxiosResponse<ResponseResult>) => AxiosResponse<ResponseResult>;
/**
* @description: 请求拦截错误处理
*/
onRequestError?: (error: AxiosError) => void;
/**
* @description: 响应拦截错误处理
*/
onResponseError?: (error: AxiosError) => void;
/**
* @description 请求失败处理
*/
onErrorCatch?: (err: AxiosError<ResponseResult>) => AxiosError | ResponseResult | undefined;
}
/**
* @interface 请求数据参数
*/
export interface RequestOptions extends AxiosRequestConfig {
/**
* @description 是否开启缓存
*/
cache?: boolean;
/**
* @description 是否开启重复请求
*/
ignoreRepeatRequests?: boolean;
}
/**
* @interface 响应数据格式
*/
export interface ResponseResult<T = any> {
code: number;
method: string;
time: string;
path: string;
data: T;
token?: string;
message?: string;
}
接下来主要是介绍一下缓存功能的实现
这里我们能看到axiosCanceler和cacheManager,对应的是取消请求和缓存管理 axosCanceler跟vben差不多,直接贴代码
js
import axios, { type AxiosRequestConfig, type Canceler } from 'axios';
export class AxiosCanceler {
private pendingMap: Map<string, Canceler> = new Map<string, Canceler>();
/**
* 获取存放map的key值
* @param config axios请求
*/
private getCancelerKey(config: AxiosRequestConfig): string {
if (config.params) {
return [config.method, config.url, ...Object.keys(config.params), ...Object.values(config.data)].join('&');
} else if (config.data) {
return [config.method, config.url, ...Object.keys(config.data), ...Object.values(config.data)].join('&');
}
return [config.method, config.url].join('&');
}
/**
* 增加请求
* @param config axios请求
*/
public addPending(config: AxiosRequestConfig): void {
// 取消重复请求
this.removePending(config);
config.cancelToken = new axios.CancelToken((cancel: Canceler) => {
const key = this.getCancelerKey(config);
if (!this.pendingMap.has(key)) {
this.pendingMap.set(key, cancel);
}
});
}
/**
* 移除请求
* @param config axios请求
*/
public removePending(config: AxiosRequestConfig): void {
const key = this.getCancelerKey(config);
const cancel = this.pendingMap.get(key);
cancel && cancel(key + '!cancel');
this.pendingMap.delete(key);
}
/**
* 移除所有请求
*/
public removeAllPending(): void {
this.pendingMap.forEach((cancel: Canceler) => {
cancel && cancel('cancel');
});
this.pendingMap.clear();
}
}
使用
对接口进行单独设置
js
import http from '../index';
export function getDictApi() {
// 开启缓存
return http.get('dict', { cache: true });
}
git
完整代码可以看这里 github.com/SLSSZM/axio...