前言
前后端项目分离的情况下,往往需要很标准的请求处理方式,以此统一处理所有的请求,来降低开发人员的心智负担,所以本篇分享一下我们项目中构建的 axios。
标准返回
先来看一个我们与后端约定的标准返回
// 成功
{
code: 200,
code_msg: 'Success',
trace_id: '1d75a6a5477b740',
resp_data: null,
data: {
token:
'MJh1MSpll-7a8D7IbD_L5IojkZBLuoQrKSTNy3aCwVHV3Kyn-EDw5Eh5c0W51xr0FUAZ6TsFjVQaWhTB9fqfXfse8qu_wtwNva8WJFy7Fz-gS-7xGNYPdXCT3xnJwbFl0MNgE3RgO_JyNXbFIS0F6zRHJwisLhbKU.cv0',
user: {
user_id: 16128,
user_name: 'd',
},
},
};
// 失败
{
code: 700000,
code_msg: '登录失败,请查核后重新登陆',
trace_id: '6d36f1ed572b32c',
resp_data: null,
data: null,
};
请求后处理
可以观察到,并不是直接使用状态码走对应的请求,无论是什么返回,都走了 200。
这样一来,无论请求成功与否,都可以拿到基本的数据结构。所以只需要额外关心两点
- 请求失败(服务器挂掉)
- 网络错误
这两点很好处理
(error) => {
if (!error.response) {
// 处理用户网络问题
Message.error('网络错误:无法连接到服务器,请检查网络连接');
} else {
// 处理服务器错误
if (error.response.status >= 500) {
Message.error('服务器开小差了,请稍后再试。');
} else {
handleError(error?.msg || '请求失败');
}
}
return Promise.reject(error);
}
再把服务正确的逻辑加上,另外我们 axios 还要负责解析接口,去掉无用的数据,所以应该直接返回给用户 response.data
axios.interceptors.response.use(
(response: AxiosResponse<HttpResponse>) => {
const { code, code_msg } = response.data;
if (code === 200) {
return response.data;
}
if (TOKEN_ERROR_CODES.includes(code)) {
Modal.error({
title: '提示',
content: '登录已过期,请先登录',
okText: '跳转',
async onOk() {
window.location.reload();
},
});
}
handleError(code_msg || '服务端返回错误');
return Promise.reject(new Error(code_msg || '服务端返回错误'));
},
(error) => {
if (!error.response) {
// 处理用户网络问题
Message.error('网络错误:无法连接到服务器,请检查网络连接');
} else {
// 处理服务器错误
if (error.response.status >= 500) {
Message.error('服务器开小差了,请稍后再试。');
} else {
handleError(error?.msg || '请求失败');
}
}
return Promise.reject(error);
}
);
请求前处理
请求前一般需要有两个环节
-
设定请求头
const handleRequestHeader = (config: InternalAxiosRequestConfig) => {
config.headers['xxxx'] = 'xxx';
return config;
};
-
让每个请求携带 token
const handleAuth = (config: InternalAxiosRequestConfig) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
};
放进前置环节中
axios.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
config = handleRequestHeader(config);
config = handleAuth(config);
return config;
},
(error) => {
return Promise.reject(error);
}
);
关于环境
对于不同的环境,我们需要打到不同的接口去,这里可以使用 process.env.NODE_ENV 来做环境的区分
export const ENV_TEST = 'https://api-ai.com/fuxi';
// 根据环境设置baseURL
// const baseURL =
// process.env.NODE_ENV === 'development'
// ? ENV_MOCK
// : ENV_TEST;
const baseURL = ENV_TEST;
axios.defaults.baseURL = baseURL;
完整代码
import { getToken } from '@/utils/auth';
import { Message, Modal } from '@arco-design/web-react';
import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import axios from 'axios';
const TOKEN_ERROR_CODES = [50008, 50012, 50014];
export const ENV_TEST = 'https://api-ai.com/fuxi';
export interface HttpResponse<T = unknown> extends AxiosResponse {
status: number;
msg?: string;
code_msg?: string;
code: number;
data: T;
}
const handleRequestHeader = (config: InternalAxiosRequestConfig) => {
// config.headers['xxxx'] = 'xxx';
return config;
};
const handleAuth = (config: InternalAxiosRequestConfig) => {
const token = getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
};
const handleError = (msg: string) => {
Message.error({ content: msg, duration: 5 * 1000 });
};
// 根据环境设置baseURL
// const baseURL =
// process.env.NODE_ENV === 'development'
// ? ENV_MOCK
// : ENV_TEST;
const baseURL = ENV_TEST;
axios.defaults.baseURL = baseURL;
axios.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
config = handleRequestHeader(config);
config = handleAuth(config);
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response: AxiosResponse<HttpResponse>) => {
const { code, code_msg } = response.data;
if (code === 200) {
return response.data;
}
if (TOKEN_ERROR_CODES.includes(code)) {
Modal.error({
title: '提示',
content: '登录已过期,请先登录',
okText: '跳转',
async onOk() {
window.location.reload();
},
});
}
handleError(code_msg || '服务端返回错误');
return Promise.reject(new Error(code_msg || '服务端返回错误'));
},
(error) => {
if (!error.response) {
// 处理用户网络问题
Message.error('网络错误:无法连接到服务器,请检查网络连接');
} else {
// 处理服务器错误
if (error.response.status >= 500) {
Message.error('服务器开小差了,请稍后再试。');
} else {
handleError(error?.msg || '请求失败');
}
}
return Promise.reject(error);
}
);
请求方法封装
如果用传统的 axios 来写无疑是冗长的,类型也不友好,如
export function GameModelApprovalList(data: GameModelApprovalListRequest) {
return axios.post<GameModelApprovalListResponse>(
'/api/v3/approval/',
data,
{
baseURL: 'https://api.com/fuxi',
}
);
}
我们一步步来优化,先来看理想的调用方式
export const login = (data: LoginData) => {
return post<LoginData, LoginRes>('/api/v1/passport/login', data);
};
有几个关键点
- 直接用 post 而不是 axios.post
- 类型要收束在泛型里
- baseUrl 应该置于请求前
根据这些点,我们封装一下 get
export const get = async <P = Params, T = any>(
url: string,
params?: P
): Promise<T> => {
const res: AxiosResponse<T> = await axios.get(url, { params });
return res.data;
};
post 也差不多,但是要额外考虑 qs 转化
export const post = async <P = Params, T = any>(
url: string,
params?: P,
qs: boolean = false
): Promise<T> => {
const requestBody = qs && params ? QS.stringify(params) : params || {};
const res: AxiosResponse<T> = await axios.post(url, requestBody);
return res.data;
};
完整代码
import axios, { AxiosResponse } from 'axios';
import QS from 'qs';
interface Params {
[key: string]: any;
}
export const get = async <P = Params, T = any>(
url: string,
params?: P
): Promise<T> => {
const res: AxiosResponse<T> = await axios.get(url, { params });
return res.data;
};
export const post = async <P = Params, T = any>(
url: string,
params?: P,
qs: boolean = false
): Promise<T> => {
const requestBody = qs && params ? QS.stringify(params) : params || {};
const res: AxiosResponse<T> = await axios.post(url, requestBody);
return res.data;
};
这样一来,我们的接口就很容易写了:
export const login = (data: LoginData) => {
return post<LoginData, LoginRes>('/api/v1/passport/login', data);
};
export const logout = () => {
return post('/api/v1/passport/logout');
};
export const getUserInfo = () => {
return post<any, UserState>('/api/v1/user/info');
};
如果这篇文章对你有帮助的话,不妨点个赞吧~
下篇预告:《【项目起步】mockjs 使用攻略》