Axios封装能改善原生框架的不足,能够智能感知参数类型,不需要每次调用时使用try-catch处理错误,能够支持路径参数替换。具体封装方法如下:
创建一个api目录和对应的子目录,用来保存所有与api调用相关的操作,目录结构如下:
config目录保存各个Apiy请求的配置信息,interceptor目录保存各个拦截器,method目录保存具体的api调用方法,request目录旋转axios请求方法,types目录保存针对axios封装扩展的一些类型
在types目录下创建ApiConfig.d.ts文件, 内容如下:
interface ApiConfig {
url: string;
useToken: boolean;
useBaseUrl: boolean;
}
export default ApiConfig;
在types目录下创建RequestConfig.d.ts文件,内容如下:
import type { AxiosRequestConfig } from 'axios';
import type ApiConfig from './ApiConfig';
interface RequestConfig extends AxiosRequestConfig {
args?: Record<string, any>;
specialConfig?: ApiConfig;
}
export default RequestConfig;
在interceptor目录下创建urlArgsReplace.ts,内容如下:
import type { InternalAxiosRequestConfig } from 'axios';
import type RequestConfig from '../types/RequestConfig';
const urlArgsReplace = {
request: {
onFulfilled: (config: InternalAxiosRequestConfig) => {
const { url, args } = config as RequestConfig;
if (args) {
const lostParams: string[] = [];
const replacedUrl = url!.replace(/\{([^}]+)\}/g, (_res, arg: string) => {
if (!args[arg]) {
lostParams.push(arg);
}
return args[arg] as string;
});
if (lostParams.length) {
return Promise.reject(new Error('在args中找不到对应的路径参数'));
}
return { ...config, url: replacedUrl };
}
return config;
},
},
};
export default urlArgsReplace;
在interceptor目录下创建文件specialRequestConfig.ts, 内容如下:
import type { InternalAxiosRequestConfig } from 'axios';
import type RequestConfig from '../types/RequestConfig';
const specialRequestConfig = {
request: {
onFulfilled: async (config: InternalAxiosRequestConfig) => {
console.log(`specialRequestConfig all config: ${JSON.stringify(config)}`);
const { specialConfig } = config as RequestConfig;
console.log(`specialRequestConfig specialConfig config: ${JSON.stringify(specialConfig)}`);
if (specialConfig) {
if (specialConfig.useBaseUrl) {
// window.webConfig.apiUrl是一个全局变量,保存了api baseURL, 可以改成自己合适的方式
config.baseURL = window.webConfig.apiUrl;
}
if (specialConfig.useToken) {
// 从localStorage中获取token, 需要改成合适的方式
const data = localStorage.getItem('ZSJTESTWEBTOKEN');
config.headers.Authorization = `Bearer ${data}`;
}
if (!config.url && specialConfig.url) {
config.url = specialConfig.url;
}
return { ...config };
}
return config;
},
},
};
export default specialRequestConfig;
在request目录下创建index.ts文件,内容如下:
import axios from 'axios';
import type { AxiosError, AxiosResponse } from 'axios';
import urlArgsReplace from '../interceptor/urlArgsReplace';
import specialRequestConfig from '../interceptor/specialRequestConfig';
import type ApiConfig from '../types/ApiConfig';
import type RequestConfig from '../types/RequestConfig';
const instance = axios.create({
timeout: 240000,
baseURL: '',
});
instance.interceptors.request.use(specialRequestConfig.request.onFulfilled, undefined);
instance.interceptors.request.use(urlArgsReplace.request.onFulfilled, undefined);
export interface ResultFormat<T = any> {
data: null | T;
error: AxiosError | null;
response: AxiosResponse<T> | null;
}
/**
* 允许定义六个可选的泛型参数:
* Payload: 用于定义响应结果的数据类型
* Data:用于定义data的数据类型
* Params:用于定义parmas的数据类型
* Args:用于定义存放路径参数的属性args的数据类型
* Headers: 用于定义额外的Header信息 (可以直接写在调用方法体内, 也可以作为泛型参数传入)
* SpecialConifg: 用于定义额外的SpecialConfig信息(可以直接写在调用方法体内, 也可以作为泛型参数传入)
*/
interface MakeRequest {
<Payload = any>(config: RequestConfig): (
requestConfig?: Partial<RequestConfig>,
) => Promise<ResultFormat<Payload>>;
<Payload, Data>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data'>> & { data: Data },
) => Promise<ResultFormat<Payload>>;
<Payload, Data, Params>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data' | 'params'>> &
(Data extends undefined ? { data?: undefined } : { data: Data }) & { params: Params },
) => Promise<ResultFormat<Payload>>;
<Payload, Data, Params, Args>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data' | 'params' | 'args'>> &
(Data extends undefined ? { data?: undefined } : { data: Data }) &
(Params extends undefined ? { params?: undefined } : { params: Params }) & {
args: Args;
},
) => Promise<ResultFormat<Payload>>;
<Payload, Data, Params, Args, Headers>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data' | 'params' | 'args' | 'headers'>> &
(Data extends undefined ? { data?: undefined } : { data: Data }) &
(Params extends undefined ? { params?: undefined } : { params: Params }) &
(Headers extends undefined ? { headers?: undefined } : { headers: Headers }) & {
args: Args;
},
) => Promise<ResultFormat<Payload>>;
<Payload, Data, Params, Args, Headers>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data' | 'params' | 'args' | 'headers' | 'specialConfig'>> &
(Data extends undefined ? { data?: undefined } : { data: Data }) &
(Params extends undefined ? { params?: undefined } : { params: Params }) &
(Headers extends undefined ? { headers?: undefined } : { headers: Headers }) & {
args: Args;
specialConfig: ApiConfig;
},
) => Promise<ResultFormat<Payload>>;
}
// eslint-disable-next-line arrow-body-style
const makeRequest: MakeRequest = <T>(config: RequestConfig) => {
return async (requestConfig?: Partial<RequestConfig>) => {
// 合并在service中定义的option和调用时从外部传入的option
const mergedConfig: RequestConfig = {
...config,
...requestConfig,
headers: {
...config.headers,
...requestConfig?.headers,
},
};
// 统一处理返回类型
try {
const response: AxiosResponse<T, RequestConfig> = await instance.request<T>(mergedConfig);
const { data } = response;
console.log(`response: ${JSON.stringify(response)}`);
return { error: null, data, response };
} catch (error: any) {
return { error, data: null, response: null };
}
};
};
export default makeRequest;
在config目录下创建requestApiConfig.ts文件,其内容如下:
import type ApiConfig from "../types/ApiConfig";
// 测试使用的api,需要修改成正式的api
const enum ApiName {
userLogin = 'userLogin',
etickets = 'etickets',
eticketstats = 'eticketstats',
testapi = 'testapi',
}
type ApiNameStrings = keyof typeof ApiName;
const requestApiConfig : Record<ApiNameStrings, ApiConfig> = {
userLogin: {
url: 'userLogins',
useToken: false,
useBaseUrl: true,
},
etickets: {
url: 'etickets',
useToken: true,
useBaseUrl: true,
},
eticketstats: {
url: 'etickets/stats',
useToken: true,
useBaseUrl: true,
},
testapi: {
url: 'values',
useToken: false,
useBaseUrl: true,
}
};
export type { ApiName, ApiNameStrings };
export default requestApiConfig;
在method目录下添加get.ts, 保存所有的get调用,其内容如下,如有其他调用,在文件内继续添加:
import makeRequest from '../request';
import requestApiConfig from '../config/requestApiConfig';
import type ETicket from '../../types/ETicket';
const method = 'get';
/* api名称手工提供,以便可以创建不在apiConfig中的记录, 或者同一api有从种get方式 */
export default {
etickets: makeRequest<ETicket[],
undefined,
{
createdDateFrom: Date; createDateTimeTo: Date; pageIndex: number; pageSize: number;
}>({
method,
specialConfig: requestApiConfig.etickets,
}),
eticketsDetail: makeRequest<
ETicket,
undefined,
undefined,
{ id: number }
>({
url: `${requestApiConfig.etickets.url}/{id}`,
specialConfig: requestApiConfig.etickets,
}),
eticketsStats: makeRequest<
number,
undefined,
{
createdDateFrom: Date; createDateTimeTo: Date; pageIndex: number; pageSize: number;
}
>({
method,
specialConfig: requestApiConfig.eticketstats,
}),
testapi: makeRequest<Array<string>
>({
method,
specialConfig: requestApiConfig.testapi,
}),
};
在method目录下添加post.ts, 保存所有的post调用,其内容如下,如有其他调用,在文件内继续添加:
import makeRequest from '../request';
import TokenResponse from '../../types/TokenResponse';
const method = 'post';
/* api名称手工提供,以便可以创建不在apiConfig中的记录 */
export default {
userLogin: makeRequest<TokenResponse,
{ username: string; password: string }>({
method,
}),
};
如果有put, delete, patch等api调用,在method下继续添加即可
在api目录下添加index.ts,将method下的所有方法统一导出
import post from './method/post';
import get from './method/get';
const api = {
get,
post,
};
export default api;
使用示例: