https://www.kancloud.cn/yunye/axios/234845

 1axios的封装
 2在vue项目中和后台交互获取数据这块我们通常使用的是axios库它是基于promise的http库可运行在浏览器端和node.js中他有很多优秀的特性例如拦截请求和响应取消请求转换json客户端防御XSRF等所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护直接推荐我们使用axios库如果还对axios不了解的可以移步axios文档
 3
 4安装
 5npm install axios; // 安装axios
 6引入
 7一般我会在项目的src目录中新建一个request文件夹然后在里面新建一个http.js和一个api.js文件http.js文件用来封装我们的axiosapi.js用来统一管理我们的接口
 8
 9// 在http.js中引入axios
10import axios from 'axios'; // 引入axios
11import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,后面会提到
12// vant的toast提示框组件,大家可根据自己的ui组件更改。
13import { Toast } from 'vant'; 
14环境的切换
15我们的项目环境可能有开发环境测试环境和生产环境我们通过node的环境变量来匹配我们的默认的接口url前缀axios.defaults.baseURL可以设置axios的默认请求地址就不多说了
16
17// 环境的切换
18if (process.env.NODE_ENV == 'development') {    
19    axios.defaults.baseURL = 'https://www.baidu.com';} 
20else if (process.env.NODE_ENV == 'debug') {    
21    axios.defaults.baseURL = 'https://www.ceshi.com';
22} 
23else if (process.env.NODE_ENV == 'production') {    
24    axios.defaults.baseURL = 'https://www.production.com';
25}
26设置请求超时
27通过axios.defaults.timeout设置默认的请求超时时间例如超过了10s就会告知用户当前请求超时请刷新等
28
29axios.defaults.timeout = 10000;
30post请求头的设置
31post请求的时候我们需要加上一个请求头所以可以在这里进行一个默认的设置即设置post的请求头为application/x-www-form-urlencoded;charset=UTF-8
32
33axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
34请求拦截
35我们在发送请求前可以进行一个请求的拦截为什么要拦截呢我们拦截请求是用来做什么的呢比如有些请求是需要用户登录之后才能访问的或者post请求的时候我们需要序列化我们提交的数据这时候我们可以在请求被发送之前进行一个拦截从而进行我们想要的操作
36
37请求拦截
38// 先导入vuex,因为我们要使用到里面的状态对象
39// vuex的路径根据自己的路径去写
40import store from '@/store/index';
41
42// 请求拦截器axios.interceptors.request.use(    
43    config => {        
44        // 每次发送请求之前判断vuex中是否存在token        
45        // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
46        // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 
47        const token = store.state.token;        
48        token && (config.headers.Authorization = token);        
49        return config;    
50    },    
51    error => {        
52        return Promise.error(error);    
53})
54这里说一下token一般是在登录完成之后将用户的token通过localStorage或者cookie存在本地然后用户每次在进入页面的时候即在main.js中),会首先从本地存储中读取token如果token存在说明用户已经登陆过则更新vuex中的token状态然后在每次请求接口的时候都会在请求的header中携带token后台人员就可以根据你携带的token来判断你的登录是否过期如果没有携带则说明没有登录过这时候或许有些小伙伴会有疑问了就是每个请求都携带token那么要是一个页面不需要用户登录就可以访问的怎么办呢其实你前端的请求可以携带token但是后台可以选择不接收啊
55
56响应的拦截
57// 响应拦截器
58axios.interceptors.response.use(    
59    response => {   
60        // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据     
61        // 否则的话抛出错误
62        if (response.status === 200) {            
63            return Promise.resolve(response);        
64        } else {            
65            return Promise.reject(response);        
66        }    
67    },    
68    // 服务器状态码不是2开头的的情况
69    // 这里可以跟你们的后台开发人员协商好统一的错误状态码    
70    // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
71    // 下面列举几个常见的操作,其他需求可自行扩展
72    error => {            
73        if (error.response.status) {            
74            switch (error.response.status) {                
75                // 401: 未登录
76                // 未登录则跳转登录页面,并携带当前页面的路径
77                // 在登录成功后返回当前页面,这一步需要在登录页操作。                
78                case 401:                    
79                    router.replace({                        
80                        path: '/login',                        
81                        query: { 
82                            redirect: router.currentRoute.fullPath 
83                        }
84                    });
85                    break;
86
87                // 403 token过期
88                // 登录过期对用户进行提示
89                // 清除本地token和清空vuex中token对象
90                // 跳转登录页面                
91                case 403:
92                     Toast({
93                        message: '登录过期,请重新登录',
94                        duration: 1000,
95                        forbidClick: true
96                    });
97                    // 清除token
98                    localStorage.removeItem('token');
99                    store.commit('loginSuccess', null);
100                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 
101                    setTimeout(() => {                        
102                        router.replace({                            
103                            path: '/login',                            
104                            query: { 
105                                redirect: router.currentRoute.fullPath 
106                            }                        
107                        });                    
108                    }, 1000);                    
109                    break; 
110
111                // 404请求不存在
112                case 404:
113                    Toast({
114                        message: '网络请求不存在',
115                        duration: 1500,
116                        forbidClick: true
117                    });
118                    break;
119                // 其他错误,直接抛出错误提示
120                default:
121                    Toast({
122                        message: error.response.data.message,
123                        duration: 1500,
124                        forbidClick: true
125                    });
126            }
127            return Promise.reject(error.response);
128        }
129    }    
130});
131响应拦截器很好理解就是服务器返回给我们的数据我们在拿到之前可以对他进行一些处理例如上面的思想如果后台返回的状态码是200则正常返回数据否则的根据错误的状态码类型进行一些我们需要的错误其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作
132
133要注意的是上面的Toast()方法是我引入的vant库中的toast轻提示组件你根据你的ui库对应使用你的一个提示组件
134
135封装get方法和post方法
136我们常用的ajax请求方法有getpostput等方法相信小伙伴都不会陌生axios对应的也有很多类似的方法不清楚的可以看下文档但是为了简化我们的代码我们还是要对其进行一个简单的封装下面我们主要封装两个方法get和post
137
138get方法我们通过定义一个get函数get函数有两个参数第一个参数表示我们要请求的url地址第二个参数是我们要携带的请求参数get函数返回一个promise对象当axios其请求成功时resolve服务器返回请求失败时reject错误值最后通过export抛出get函数
139
140/**
141 * get方法,对应get请求
142 * @param {String} url [请求的url地址]
143 * @param {Object} params [请求时携带的参数]
144 */
145export function get(url, params){    
146    return new Promise((resolve, reject) =>{        
147        axios.get(url, {            
148            params: params        
149        }).then(res => {
150            resolve(res.data);
151        }).catch(err =>{
152            reject(err.data)        
153    })    
154});}
155post方法原理同get基本一样但是要注意的是post方法必须要使用对提交从参数对象进行序列化的操作所以这里我们通过node的qs模块来序列化我们的参数这个很重要如果没有序列化操作后台是拿不到你提交的数据的这就是文章开头我们import QS from 'qs';的原因如果不明白序列化是什么意思的就百度一下吧答案一大堆
156
157/** 
158 * post方法,对应post请求 
159 * @param {String} url [请求的url地址] 
160 * @param {Object} params [请求时携带的参数] 
161 */
162export function post(url, params) {
163    return new Promise((resolve, reject) => {
164         axios.post(url, QS.stringify(params))
165        .then(res => {
166            resolve(res.data);
167        })
168        .catch(err =>{
169            reject(err.data)
170        })
171    });
172}
173这里有个小细节说下axios.get()方法和axios.post()在提交数据时参数的书写方式还是有区别的区别就是get的第二个参数是一个{},然后这个对象的params属性值是一个参数对象的而post的第二个参数就是一个参数对象两者略微的区别要留意哦
174
175axios的封装基本就完成了下面再简单说下api的统一管理
176整齐的api就像电路板一样即使再复杂也能很清晰整个线路上面说了我们会新建一个api.js,然后在这个文件中存放我们所有的api接口
177
178首先我们在api.js中引入我们封装的get和post方法
179
180/**   
181 * api接口统一管理
182 */
183import { get, post } from './http'
184现在例如我们有这样一个接口是一个post请求
185
186http://www.baiodu.com/api/v1/users/my_address/address_edit_before
187我们可以在api.js中这样封装
188
189export const apiAddress = p => post('api/v1/users/my_address/address_edit_before', p);
190我们定义了一个apiAddress方法这个方法有一个参数pp是我们请求接口时携带的参数对象而后调用了我们封装的post方法post方法的第一个参数是我们的接口地址第二个参数是apiAddress的p参数即请求接口时携带的参数对象最后通过export导出apiAddress
191
192然后在我们的页面中可以这样调用我们的api接口
193
194import { apiAddress } from '@/request/api';// 导入我们的api接口
195export default {        
196    name: 'Address',    
197    created () {
198        this.onLoad();
199    },
200    methods: {            
201        // 获取数据            
202        onLoad() {
203            // 调用api接口,并且提供了两个参数                
204            apiAddress({                    
205                type: 0,                    
206                sort: 1                
207            }).then(res => {
208                // 获取数据成功后的其他操作
209                ………………                
210            })            
211        }        
212    }
213}
214其他的api接口就在pai.js中继续往下面扩展就可以了友情提示为每个接口写好注释哦!!!
215
216api接口管理的一个好处就是我们把api统一集中起来如果后期需要修改接口我们就直接在api.js中找到对应的修改就好了而不用去每一个页面查找我们的接口然后再修改会很麻烦关键是万一修改的量比较大就规格gg了还有就是如果直接在我们的业务代码修改接口一不小心还容易动到我们的业务代码造成不必要的麻烦
217
218好了最后把完成的axios封装代码奉上
219
220/**axios封装
221 * 请求拦截、相应拦截、错误统一处理
222 */
223import axios from 'axios';import QS from 'qs';
224import { Toast } from 'vant';
225import store from '../store/index'
226
227// 环境的切换
228if (process.env.NODE_ENV == 'development') {    
229    axios.defaults.baseURL = '/api';
230} else if (process.env.NODE_ENV == 'debug') {    
231    axios.defaults.baseURL = '';
232} else if (process.env.NODE_ENV == 'production') {    
233    axios.defaults.baseURL = 'http://api.123dailu.com/';
234}
235
236// 请求超时时间
237axios.defaults.timeout = 10000;
238
239// post请求头
240axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
241
242// 请求拦截器
243axios.interceptors.request.use(    
244    config => {
245        // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
246        // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
247        const token = store.state.token;        
248        token && (config.headers.Authorization = token);        
249        return config;    
250    },    
251    error => {        
252        return Promise.error(error);    
253    })
254
255// 响应拦截器
256axios.interceptors.response.use(    
257    response => {        
258        if (response.status === 200) {            
259            return Promise.resolve(response);        
260        } else {            
261            return Promise.reject(response);        
262        }    
263    },
264    // 服务器状态码不是200的情况    
265    error => {        
266        if (error.response.status) {            
267            switch (error.response.status) {                
268                // 401: 未登录                
269                // 未登录则跳转登录页面,并携带当前页面的路径                
270                // 在登录成功后返回当前页面,这一步需要在登录页操作。                
271                case 401:                    
272                    router.replace({                        
273                        path: '/login',                        
274                        query: { redirect: router.currentRoute.fullPath } 
275                    });
276                    break;
277                // 403 token过期                
278                // 登录过期对用户进行提示                
279                // 清除本地token和清空vuex中token对象                
280                // 跳转登录页面                
281                case 403:                     
282                    Toast({                        
283                        message: '登录过期,请重新登录',                        
284                        duration: 1000,                        
285                        forbidClick: true                    
286                    });                    
287                    // 清除token                    
288                    localStorage.removeItem('token');                    
289                    store.commit('loginSuccess', null);                    
290                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
291                    setTimeout(() => {                        
292                        router.replace({                            
293                            path: '/login',                            
294                            query: { 
295                                redirect: router.currentRoute.fullPath 
296                            }                        
297                        });                    
298                    }, 1000);                    
299                    break; 
300                // 404请求不存在                
301                case 404:                    
302                    Toast({                        
303                        message: '网络请求不存在',                        
304                        duration: 1500,                        
305                        forbidClick: true                    
306                    });                    
307                break;                
308                // 其他错误,直接抛出错误提示                
309                default:                    
310                    Toast({                        
311                        message: error.response.data.message,                        
312                        duration: 1500,                        
313                        forbidClick: true                    
314                    });            
315            }            
316            return Promise.reject(error.response);        
317        }       
318    }
319);
320/** 
321 * get方法,对应get请求 
322 * @param {String} url [请求的url地址] 
323 * @param {Object} params [请求时携带的参数] 
324 */
325export function get(url, params){    
326    return new Promise((resolve, reject) =>{        
327        axios.get(url, {            
328            params: params        
329        })        
330        .then(res => {            
331            resolve(res.data);        
332        })        
333        .catch(err => {            
334            reject(err.data)        
335        })    
336    });
337}
338/** 
339 * post方法,对应post请求 
340 * @param {String} url [请求的url地址] 
341 * @param {Object} params [请求时携带的参数] 
342 */
343export function post(url, params) {    
344    return new Promise((resolve, reject) => {         
345        axios.post(url, QS.stringify(params))        
346        .then(res => {            
347            resolve(res.data);        
348        })        
349        .catch(err => {            
350            reject(err.data)        
351        })    
352    });
353}
354axios的封装根据需求的不同而不同
3551.优化axios封装去掉之前的get和post
356
3572.断网情况处理
358
3593.更加模块化的api管理
360
3614.接口域名有多个的情况
362
3635.api挂载到vue.prototype上省去引入的步骤
364
365http.js中axios封装的优化先直接贴代码
366
367/**
368 * axios封装
369 * 请求拦截、响应拦截、错误统一处理
370 */
371import axios from 'axios';
372import router from '../router';
373import store from '../store/index';
374import { Toast } from 'vant';
375
376/** 
377 * 提示函数 
378 * 禁止点击蒙层、显示一秒后关闭
379 */
380const tip = msg => {    
381    Toast({        
382        message: msg,        
383        duration: 1000,        
384        forbidClick: true    
385    });
386}
387
388/** 
389 * 跳转登录页
390 * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
391 */
392const toLogin = () => {
393    router.replace({
394        path: '/login',        
395        query: {
396            redirect: router.currentRoute.fullPath
397        }
398    });
399}
400
401/** 
402 * 请求失败后的错误统一处理 
403 * @param {Number} status 请求失败的状态码
404 */
405const errorHandle = (status, other) => {
406    // 状态码判断
407    switch (status) {
408        // 401: 未登录状态,跳转登录页
409        case 401:
410            toLogin();
411            break;
412        // 403 token过期
413        // 清除token并跳转登录页
414        case 403:
415            tip('登录过期,请重新登录');
416            localStorage.removeItem('token');
417            store.commit('loginSuccess', null);
418            setTimeout(() => {
419                toLogin();
420            }, 1000);
421            break;
422        // 404请求不存在
423        case 404:
424            tip('请求的资源不存在'); 
425            break;
426        default:
427            console.log(other);   
428        }}
429
430// 创建axios实例
431var instance = axios.create({    timeout: 1000 * 12});
432// 设置post请求头
433instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
434/** 
435 * 请求拦截器 
436 * 每次请求前,如果存在token则在请求头中携带token 
437 */ 
438instance.interceptors.request.use(    
439    config => {        
440        // 登录流程控制中,根据本地是否存在token判断用户的登录情况        
441        // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token        
442        // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码        
443        // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。        
444        const token = store.state.token;        
445        token && (config.headers.Authorization = token);        
446        return config;    
447    },    
448    error => Promise.error(error))
449
450// 响应拦截器
451instance.interceptors.response.use(    
452    // 请求成功
453    res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),    
454    // 请求失败
455    error => {
456        const { response } = error;
457        if (response) {
458            // 请求已发出,但是不在2xx的范围 
459            errorHandle(response.status, response.data.message);
460            return Promise.reject(response);
461        } else {
462            // 处理断网的情况
463            // eg:请求超时或断网时,更新state的network状态
464            // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
465            // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
466            if (!window.navigator.onLine) {
467               store.commit('changeNetwork', false);
468            } else {
469                return Promise.reject(error);
470            }
471        }
472    });
473
474export default instance;
475这个axios和之前的大同小异做了如下几点改变
476
4771.去掉了之前get和post方法的封装通过创建一个axios实例然后export default方法导出这样使用起来更灵活一些
478
4792.去掉了通过环境变量控制baseUrl的值考虑到接口会有多个不同域名的情况所以准备通过js变量来控制接口域名这点具体在api里会介绍
480
4813.增加了请求超时即断网状态的处理说下思路当断网时通过更新vuex中network的状态来控制断网提示组件的显示隐藏断网提示一般会有重新加载数据的操作这步会在后面对应的地方介绍
482
4834.公用函数进行抽出简化代码尽量保证单一职责原则
484
485下面说下api这块考虑到一下需求
486
4871.更加模块化
488
4892.更方便多人开发有效减少解决命名冲突
490
4913.处理接口域名有多个情况
492
493这里这里呢新建了一个api文件夹里面有一个index.js和一个base.js以及多个根据模块划分的接口js文件index.js是一个api的出口base.js管理接口域名其他js则用来管理各个模块的接口
494
495先放index.js代码
496
497/** 
498 * api接口的统一出口
499 */
500// 文章模块接口
501import article from '@/api/article';
502// 其他模块的接口……
503
504// 导出接口
505export default {    
506    article,
507    // ……
508}
509index.js是一个api接口的出口这样就可以把api接口根据功能划分为多个模块利于多人协作开发比如一个人只负责一个模块的开发等还能方便每个模块中接口的命名哦
510
511base.js:
512
513/**
514 * 接口域名的管理
515 */
516const base = {    
517    sq: 'https://xxxx111111.com/api/v1',    
518    bd: 'http://xxxxx22222.com/api'
519}
520
521export default base;
522通过base.js来管理我们的接口域名不管有多少个都可以通过这里进行接口的定义即使修改起来也是很方便的
523
524最后就是接口模块的说明例如上面的article.js:
525
526/**
527 * article模块接口列表
528 */
529
530import base from './base'; // 导入接口域名列表
531import axios from '@/utils/http'; // 导入http中创建的axios实例
532import qs from 'qs'; // 根据需求是否导入qs模块
533
534const article = {    
535    // 新闻列表    
536    articleList () {        
537        return axios.get(`${base.sq}/topics`);    
538    },    
539    // 新闻详情,演示    
540    articleDetail (id, params) {        
541        return axios.get(`${base.sq}/topic/${id}`, {            
542            params: params        
543        });    
544    },
545    // post提交    
546    login (params) {        
547        return axios.post(`${base.sq}/accesstoken`, qs.stringify(params));    
548    }
549    // 其他接口…………
550}
551
552export default article;
5531.通过直接引入我们封装好的axios实例然后定义接口调用axios实例并返回可以更灵活的使用axios比如你可以对post请求时提交的数据进行一个qs序列化的处理等
554
5552.请求的配置更灵活你可以针对某个需求进行一个不同的配置关于配置的优先级axios文档说的很清楚这个顺序是 lib/defaults.js 找到的库的默认值然后是实例的 defaults 属性最后是请求的 config 参数后者将优先于前者
556
5573.restful风格的接口也可以通过这种方式灵活的设置api接口地址
558
559最后为了方便api的调用我们需要将其挂载到vue的原型上在main.js中
560
561import Vue from 'vue'
562import App from './App'
563import router from './router' // 导入路由文件
564import store from './store' // 导入vuex文件
565import api from './api' // 导入api接口
566
567Vue.prototype.$api = api; // 将api挂载到vue的原型上
568然后我们可以在页面中这样调用接口eg
569
570methods: {    
571    onLoad(id) {      
572        this.$api.article.articleDetail(id, {        
573            api: 123      
574        }).then(res=> {
575            // 执行某些操作      
576        })    
577    }  
578}
579再提一下断网的处理这里只做一个简单的示例
580
581<template>  
582    <div id="app">    
583        <div v-if="!network">      
584            <h3>我没网了</h3>      
585            <div @click="onRefresh">刷新</div>      
586        </div>    
587        <router-view/>      
588    </div>
589</template>
590
591<script>
592    import { mapState } from 'vuex';
593    export default {  
594        name: 'App',  
595        computed: {    
596            ...mapState(['network'])  
597        },  
598        methods: {    
599            // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
600            onRefresh () {      
601                this.$router.replace('/refresh')    
602            }  
603        }
604    }
605</script>
606这是app.vue,这里简单演示一下断网。在http.js中介绍了,我们会在断网的时候,来更新vue中network的状态,那么这里我们根据network的状态来判断是否需要加载这个断网组件。断网情况下,加载断网组件,不加载对应页面的组件。当点击刷新的时候,我们通过跳转refesh页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。
607
608
609// refresh.vue
610beforeRouteEnter (to, from, next) {
611    next(vm => {            
612        vm.$router.replace(from.fullPath)        
613    })    
614}
个人笔记记录 2021 ~ 2025