Vue系列三:封装axios

一般在开发项目的时候,使用的http请求库为 axios,这里总结下axios的封装.

  • 前端http请求属于services模块,在项目中创建core/services/api目录,在里面实现axios的封装
  • 其次,创建core/interceptor/auth.interceptor.js,实现拦截器。

准备工作

先了解axiosapi,这里附上地址https://github.com/axios/axios.

开始封装

  • services/api/rest.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import axios from 'axios';
import Qs from 'qs';

class REST {
constructor(endPointURL, config = {}) {
// axios 配置信息
const axiosConfig = {
baseURL: endPointURL,
...config,
paramsSerializer(params) {
return Qs.stringify(params, {
arrayFormat: 'repeat',
skipNulls: true,
});
},
};
this.endPointURL = endPointURL;
this.rest = axios.create(axiosConfig); // axios.create() 创建一个axios实例。
}

/**
* export original axios request function.
* @param config see https://github.com/axios/axios#axiosrequestconfig-1
*/
request(config) {
return this.rest.request(config);
}

/**
* HTTP METHOD GET
* @param {String} url
* @param {Object} params
* @param {Object} config
*/
get(url, params, config) {
const getConfig = {};
if (params) Object.assign(getConfig, { params });
if (config) Object.assign(getConfig, config);

return this.rest.get(url, getConfig);
}

/**
* HTTP METHOD POST
* @param {String} url
* @param {Object} data
* @param {Object} config
*/
post(url, data, config) {
return this.rest.post(url, data, config);
}

/**
* HTTP METHOD DELETE
* @param {String} url
* @param {Object} config
*/
delete(url, params, config) {
const delConfig = {};
if (params) Object.assign(delConfig, { params });
if (config) Object.assign(delConfig, config);

return this.rest.delete(url, delConfig);
}

/**
* HTTP MEHTOD PUT
* @param {String} url
* @param {Object} data
* @param {Object} config
*/
put(url, data, config) {
return this.rest.put(url, data, config);
}

/**
* HTTP MEHTOD PATCH
* @param {String} url
* @param {Object} data
* @param {Object} config
*/
patch(url, data, config) {
return this.rest.patch(url, data, config);
}

createCancelSource() {
return axios.CancelToken.source();
}

useInterceptor(interceptor) {
const {
request,
response,
responseError,
} = interceptor;
if (request) {
this.rest.interceptors.request.use(request);
}
if (response || responseError) {
this.rest.interceptors.response.use(
response,
responseError,
);
}
}
}

export default REST;
  • core/interceptor/auth.interceptor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import Vue from 'vue';
import { get as getValue } from 'lodash';
import AuthService from '@/core/services/auth.service';

/**
* auth service handle global authrization.
*/
export default {
request(config) {
if (!config.headers.Authorization && AuthService.getToken()) {
config.headers.Authorization = `Bearer ${AuthService.getToken()}`;
}
return config;
},

// global ajax success handler
response(res) {
if (/^20\d/.test(res.status)) {
return res.data;
}
return res;
},

// global ajax error handler
responseError(error) {
// error reponse
const { response = {} } = error;
switch (response.status) {
case 502:
Vue.noty.error('后端出问题了, 请联系管理员');
break;
case 401:
AuthService.logout();
if (Vue.$router) {
Vue.$router.push({
name: 'home',
});
}
break;
case 403:
if (getValue(response, 'headers.Authorization')) {
Vue.noty.error('权限不足');
} else {
Vue.noty.error(getValue(response, 'data.error_info'));
}
break;
default:
Vue.noty.error(getValue(response, 'data.error_info', '请求发生错误'));
}
return Promise.reject(response);
},
};
  • services/api/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import REST from './rest';
import AuthInterceptor from '../../interceptors/auth.interceptor';

const API_URL = process.env.VUE_APP_API_URL;

class APIService extends REST {
constructor() {
super(`${API_URL}/v1`);
this.useInterceptor(AuthInterceptor);
}

getEndPointURL() {
return this.endPointURL;
}

create(url = API_URL, config) {
return new REST(url, config);
}
}

export default new APIService();

使用

  • 每一个功能模块都有要使用的api接口。这些接口都放在services目录里。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// user.service.js
import api from './api';

/**
* @kind api.service
*/
class UserService {
constructor() {
this.api = api;
}

// user
createUser(user) {
return this.api.post('/users', user);
}

updateUser(userId, user) {
return this.api.patch(`/users/${userId}`, user);
}

/**
* 根据关键词获取用户列表
* @param {String} q 搜索关键词
*/
getUsers(q) {
let params;
if (q || q === '') {
params = { q };
}
return this.api.get('/users', params);
}

export default new UserService();
  • 使用api发送http请求:

例如:

1
2
3
4
5
6
7
8
import UserService from '@/core/services/user.service';

// ...
loadUsers() {
return UserService.getUsers().then(users => {
// ....
})
}

Restful api 的传参问题

在此之前,附一段ts表达的 axios instance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export interface AxiosInstance {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
defaults: AxiosRequestConfig;
interceptors: {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
};
getUri(config?: AxiosRequestConfig): string;
request<T = any, R = AxiosResponse<T>> (config: AxiosRequestConfig): Promise<R>;
get<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
delete<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
head<T = any, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig): Promise<R>;
post<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
put<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
patch<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
}

可以看到 get、delete、head的传参格式是一样的,post、put、patch的传参格式是一样的

  • get: get请求一般不带request body, 但是带 queryString 作为数据的 filter

例如:

1
2
3
getSomething(query = {}) { // 带 queryString
return this.api.get(`/xxx/xxx`, query);
}

  • post: 用于添加数据。要带request body,多用于表单,也用于一些复杂的查询接口

例如:

1
2
3
createSomething(id, something) {
return this.api.post(`/xxx/${id}/xxx`, something);
}

  • delete: 用于删除数据。如果要支持批量删除的话,则要带请求体

例如:

1
2
3
4
5
6
7
8
deleteSomething(id) { //删除单个
return this,api.delete(`/xxx/xxx/${id}`);
}

batchDeleteSomething(ids = []) { //批量删除
const params = { data: ids };
return this.api.delete(`/xxx/xxx`, null, params);
}

  • patch: 更新数据(客户端提供改变的属性)。

例如:

1
2
3
4
5
6
7
8
9
10
updateSomething(id, something) { // 仅带 request body
return this.api.patch(`/xxx/xxx/${id}`, something);
}

updateSomething(id, something) { // 带 request body 和 queryString
const params = { search_type: 'xxx' };
return this.api.patch(`/xxx/xxx/${id}`, something, {
params,
});
}

  • put: 更新数据(客户端提供改变后的完整资源)。

例如:同post