系列目录
- 手摸手创建一个 Vue + Ts 项目(一) —— 初始化项目
- 手摸手创建一个 Vue + Ts 项目(二) —— 配置路由
- 手摸手创建一个 Vue + Ts 项目(三) —— 实现一个左侧菜单栏
- 手摸手创建一个 Vue + Ts 项目(四) —— 完善左侧菜单栏
- 手摸手创建一个 Vue + Ts 项目(五) —— UnoCSS 的基本使用
- 手摸手创建一个 Vue + Ts 项目(六) —— 完善布局
- 手摸手创建一个 Vue + Ts 项目(七)—— 封装全局反馈组件
前言
虽然 axios 功能已经非常强大,但在实际的 axios 使用过程中,通过会针对接口来做一些通用的适配封装,这里主要在基本功能的基础上增加一些通用的方法、钩子和异常处理。
定义相关类型
在 src 文件夹中,创建 utils/http 文件夹,创建 types.ts 文件,存放封装用到的类型:
import type { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
/**
* 接口通用返回参数
*/
export interface ApiResponse<T> {
code: number,
message: string,
data: T
}
/**
* 请求配置属性
*/
export interface RequestOptions {
// 是否展示 Loading 动画
showLoading?: boolean,
// 是否弹窗展示异常信息
showErrorMessage?: boolean,
// 是否进行数据转换
transform?: boolean
}
/**
* 拦截器钩子
*/
export interface InterceptorHooks {
beforeRequestCallback?: (request: ExpandAxiosRequestConfig) => void
beforeResponseCallback?: (response: ExpandAxiosResponse) => void
}
/**
* 拓展自定义请求配置
*/
export interface ExpandAxiosRequestConfig<D = any> extends AxiosRequestConfig<D> {
interceptorHooks?: InterceptorHooks
requestOptions?: RequestOptions
}
/**
* 拓展 axios 请求配置
*/
export interface ExpandInternalAxiosRequestConfig<D = any> extends InternalAxiosRequestConfig<D> {
interceptorHooks?: InterceptorHooks
requestOptions?: RequestOptions
}
/**
* 拓展 axios 返回配置
*/
export interface ExpandAxiosResponse<T = any, D = any> extends AxiosResponse<T, D> {
config: ExpandInternalAxiosRequestConfig<D>
}
这里创建了几个接口类型,这里针对每个类型进行说明。
ApiResponse<T>:对于后端接口结构的一个封装,可以根据实际项目进行调整;RequestOptions:额外添加的请求配置属性InterceptorHooks:拦截器钩子,分为请求之前和响应之前两个ExpandAxiosRequestConfig:axios实际请求时的一个补充,将前面封装的RequestOptions和InterceptorHooks添加上ExpandInternalAxiosRequestConfig:axios 请求拦截器中的子类ExpandAxiosResponse:axios 响应拦截器中的类型子类
封装请求对象
创建 /src/utils/http/AxiosRequest.ts 文件:
import type { AxiosInstance, AxiosResponse } from "axios";
import axios from "axios";
import {
ApiResponse,
ExpandAxiosRequestConfig,
ExpandAxiosResponse,
ExpandInternalAxiosRequestConfig,
} from "./types";
const errorStateMap = new Map([
[400, "请求方式错误"],
[401, "请重新登录"],
[403, "拒绝访问"],
[404, "请求地址不存在"],
[500, "服务器出错"],
]);
const errorHandler = (err: any) => {
if (err.config?.requestOptions.showLoading) {
window.$loadingBar.error();
}
if (err.config?.requestOptions.showErrorMessage) {
const message: string =
errorStateMap.get(err.response.status) || "请求出错,请稍后重试";
handleErrorMessage(message);
}
return Promise.reject(err);
};
const handleErrorMessage = (errorMessage: string) => {
window.$message.error(errorMessage);
};
const successCode = 1;
export class AxiosRequest {
private defaultConfig: ExpandAxiosRequestConfig = {
// 请求超时时间
timeout: 3000,
requestOptions: {
showLoading: true,
showErrorMessage: true,
transform: true,
},
};
private axiosInstance: AxiosInstance;
public constructor(config: ExpandAxiosRequestConfig = {}) {
const axiosConfig = Object.assign(this.defaultConfig, config);
this.axiosInstance = axios.create(axiosConfig);
this.interceptRequest();
this.interceptResponse();
}
/**
* 通用请求方法
*/
public request<D, R>(
config: ExpandAxiosRequestConfig<D>
): Promise<AxiosResponse<R>> {
return this.axiosInstance.request(config);
}
/**
* get 请求
*/
public get<D, R>(
url: string,
config: ExpandAxiosRequestConfig<D> = {}
): Promise<R> {
let requestOptions = config.requestOptions
if (requestOptions) {
requestOptions.transform = true
config.requestOptions = requestOptions;
}
return this.axiosInstance.get(url, config);
}
/**
* get 请求
*/
public getNoTransRes<D, R>(
url: string,
config: ExpandAxiosRequestConfig<D> = {}
): Promise<ApiResponse<R>> {
let requestOptions = config.requestOptions
if (!requestOptions) {
requestOptions = {}
}
requestOptions.transform = false
config.requestOptions = requestOptions;
return this.axiosInstance.get(url, config);
}
/**
* post 请求
*/
public post<D, R>(
url: string,
data?: D,
config: ExpandAxiosRequestConfig<D> = {}
): Promise<R> {
let requestOptions = config.requestOptions
if (requestOptions) {
requestOptions.transform = true
config.requestOptions = requestOptions;
}
return this.axiosInstance.post(url, data, config);
}
/**
* post 请求
*/
public postNoTransRes<D, R>(
url: string,
data?: D,
config: ExpandAxiosRequestConfig<D> = {}
): Promise<ApiResponse<R>> {
let requestOptions = config.requestOptions
if (!requestOptions) {
requestOptions = {}
}
requestOptions.transform = false
config.requestOptions = requestOptions;
return this.axiosInstance.post(url, data, config);
}
private interceptRequest(): void {
this.axiosInstance.interceptors.request.use(
async (config: ExpandInternalAxiosRequestConfig) => {
// loadingBar
if (config.requestOptions?.showLoading) {
window.$loadingBar.start();
}
// hook
if (config.interceptorHooks?.beforeRequestCallback) {
config.interceptorHooks.beforeRequestCallback(config);
}
return config;
},
errorHandler
);
}
private interceptResponse(): void {
this.axiosInstance.interceptors.response.use(
async (response: ExpandAxiosResponse): Promise<any> => {
// loadingBar
if (response.config.requestOptions?.showLoading) {
window.$loadingBar.finish();
}
// hook
if (response.config.interceptorHooks?.beforeResponseCallback) {
response.config.interceptorHooks.beforeResponseCallback(response);
}
// transform data
if (!response.config.requestOptions?.transform) {
return response;
}
const { code, message, data } = response.data;
if (code === successCode) {
return data;
} else {
if (response.config.requestOptions?.showErrorMessage) {
handleErrorMessage(message);
}
}
return Promise.reject(response.data);
},
errorHandler
);
}
}
之后,在 src/utils/http 文件夹下创建 index.ts 文件,用于请求对象的进一步封装。
import { AxiosRequest } from "./AxiosRequest"
export default new AxiosRequest({
baseURL: '/api'
})
使用
在使用时,可以分别定义入参和出参的类型。
例如,这里要查询用户列表:
- 封装出入参类型
export interface UserQuery {
pageIndex: number,
pageSize: number
}
export interface UserInfo {
userId: string,
username: string,
nickName: string,
phoneNumber: string,
state: string,
loginIp?: string,
loginDate?: string,
remark?: string
}
- 封装请求
import { UserQuery, UserInfo, UserAddRequest } from "./types/userTypes";
import request from '@/utils/http'
export const userPageQuery = (data: UserQuery) => {
return request.post<UserQuery, UserInfo>('user/pageQuery', data)
};
export const userAdd = (data: UserAddRequest) => {
return request.post<UserAddRequest, void>('user/add', data)
}