首页/文章/前端工具

axios

2021-11-30
421 分钟
...

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

axios的封装
在vue项目中和后台交互获取数据这块我们通常使用的是axios库它是基于promise的http库可运行在浏览器端和node.js中他有很多优秀的特性例如拦截请求和响应取消请求转换json客户端防御XSRF等所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护直接推荐我们使用axios库如果还对axios不了解的可以移步axios文档

安装
npm install axios; // 安装axios
引入
一般我会在项目的src目录中新建一个request文件夹然后在里面新建一个http.js和一个api.js文件http.js文件用来封装我们的axiosapi.js用来统一管理我们的接口

// 在http.js中引入axios
import axios from 'axios'; // 引入axios
import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,后面会提到
// vant的toast提示框组件,大家可根据自己的ui组件更改。
import { Toast } from 'vant'; 
环境的切换
我们的项目环境可能有开发环境测试环境和生产环境我们通过node的环境变量来匹配我们的默认的接口url前缀axios.defaults.baseURL可以设置axios的默认请求地址就不多说了

// 环境的切换
if (process.env.NODE_ENV == 'development') {    
    axios.defaults.baseURL = 'https://www.baidu.com';} 
else if (process.env.NODE_ENV == 'debug') {    
    axios.defaults.baseURL = 'https://www.ceshi.com';
} 
else if (process.env.NODE_ENV == 'production') {    
    axios.defaults.baseURL = 'https://www.production.com';
}
设置请求超时
通过axios.defaults.timeout设置默认的请求超时时间例如超过了10s就会告知用户当前请求超时请刷新等

axios.defaults.timeout = 10000;
post请求头的设置
post请求的时候我们需要加上一个请求头所以可以在这里进行一个默认的设置即设置post的请求头为application/x-www-form-urlencoded;charset=UTF-8

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
请求拦截
我们在发送请求前可以进行一个请求的拦截为什么要拦截呢我们拦截请求是用来做什么的呢比如有些请求是需要用户登录之后才能访问的或者post请求的时候我们需要序列化我们提交的数据这时候我们可以在请求被发送之前进行一个拦截从而进行我们想要的操作

请求拦截
// 先导入vuex,因为我们要使用到里面的状态对象
// vuex的路径根据自己的路径去写
import store from '@/store/index';

// 请求拦截器axios.interceptors.request.use(    
    config => {        
        // 每次发送请求之前判断vuex中是否存在token        
        // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
        // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 
        const token = store.state.token;        
        token && (config.headers.Authorization = token);        
        return config;    
    },    
    error => {        
        return Promise.error(error);    
})
这里说一下token一般是在登录完成之后将用户的token通过localStorage或者cookie存在本地然后用户每次在进入页面的时候即在main.js中),会首先从本地存储中读取token如果token存在说明用户已经登陆过则更新vuex中的token状态然后在每次请求接口的时候都会在请求的header中携带token后台人员就可以根据你携带的token来判断你的登录是否过期如果没有携带则说明没有登录过这时候或许有些小伙伴会有疑问了就是每个请求都携带token那么要是一个页面不需要用户登录就可以访问的怎么办呢其实你前端的请求可以携带token但是后台可以选择不接收啊

响应的拦截
// 响应拦截器
axios.interceptors.response.use(    
    response => {   
        // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据     
        // 否则的话抛出错误
        if (response.status === 200) {            
            return Promise.resolve(response);        
        } else {            
            return Promise.reject(response);        
        }    
    },    
    // 服务器状态码不是2开头的的情况
    // 这里可以跟你们的后台开发人员协商好统一的错误状态码    
    // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
    // 下面列举几个常见的操作,其他需求可自行扩展
    error => {            
        if (error.response.status) {            
            switch (error.response.status) {                
                // 401: 未登录
                // 未登录则跳转登录页面,并携带当前页面的路径
                // 在登录成功后返回当前页面,这一步需要在登录页操作。                
                case 401:                    
                    router.replace({                        
                        path: '/login',                        
                        query: { 
                            redirect: router.currentRoute.fullPath 
                        }
                    });
                    break;

                // 403 token过期
                // 登录过期对用户进行提示
                // 清除本地token和清空vuex中token对象
                // 跳转登录页面                
                case 403:
                     Toast({
                        message: '登录过期,请重新登录',
                        duration: 1000,
                        forbidClick: true
                    });
                    // 清除token
                    localStorage.removeItem('token');
                    store.commit('loginSuccess', null);
                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面 
                    setTimeout(() => {                        
                        router.replace({                            
                            path: '/login',                            
                            query: { 
                                redirect: router.currentRoute.fullPath 
                            }                        
                        });                    
                    }, 1000);                    
                    break; 

                // 404请求不存在
                case 404:
                    Toast({
                        message: '网络请求不存在',
                        duration: 1500,
                        forbidClick: true
                    });
                    break;
                // 其他错误,直接抛出错误提示
                default:
                    Toast({
                        message: error.response.data.message,
                        duration: 1500,
                        forbidClick: true
                    });
            }
            return Promise.reject(error.response);
        }
    }    
});
响应拦截器很好理解就是服务器返回给我们的数据我们在拿到之前可以对他进行一些处理例如上面的思想如果后台返回的状态码是200则正常返回数据否则的根据错误的状态码类型进行一些我们需要的错误其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作

要注意的是上面的Toast()方法是我引入的vant库中的toast轻提示组件你根据你的ui库对应使用你的一个提示组件

封装get方法和post方法
我们常用的ajax请求方法有getpostput等方法相信小伙伴都不会陌生axios对应的也有很多类似的方法不清楚的可以看下文档但是为了简化我们的代码我们还是要对其进行一个简单的封装下面我们主要封装两个方法get和post

get方法我们通过定义一个get函数get函数有两个参数第一个参数表示我们要请求的url地址第二个参数是我们要携带的请求参数get函数返回一个promise对象当axios其请求成功时resolve服务器返回请求失败时reject错误值最后通过export抛出get函数

/**
 * get方法,对应get请求
 * @param {String} url [请求的url地址]
 * @param {Object} params [请求时携带的参数]
 */
export function get(url, params){    
    return new Promise((resolve, reject) =>{        
        axios.get(url, {            
            params: params        
        }).then(res => {
            resolve(res.data);
        }).catch(err =>{
            reject(err.data)        
    })    
});}
post方法原理同get基本一样但是要注意的是post方法必须要使用对提交从参数对象进行序列化的操作所以这里我们通过node的qs模块来序列化我们的参数这个很重要如果没有序列化操作后台是拿不到你提交的数据的这就是文章开头我们import QS from 'qs';的原因如果不明白序列化是什么意思的就百度一下吧答案一大堆

/** 
 * post方法,对应post请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function post(url, params) {
    return new Promise((resolve, reject) => {
         axios.post(url, QS.stringify(params))
        .then(res => {
            resolve(res.data);
        })
        .catch(err =>{
            reject(err.data)
        })
    });
}
这里有个小细节说下axios.get()方法和axios.post()在提交数据时参数的书写方式还是有区别的区别就是get的第二个参数是一个{},然后这个对象的params属性值是一个参数对象的而post的第二个参数就是一个参数对象两者略微的区别要留意哦

axios的封装基本就完成了下面再简单说下api的统一管理
整齐的api就像电路板一样即使再复杂也能很清晰整个线路上面说了我们会新建一个api.js,然后在这个文件中存放我们所有的api接口

首先我们在api.js中引入我们封装的get和post方法

/**   
 * api接口统一管理
 */
import { get, post } from './http'
现在例如我们有这样一个接口是一个post请求

http://www.baiodu.com/api/v1/users/my_address/address_edit_before
我们可以在api.js中这样封装

export const apiAddress = p => post('api/v1/users/my_address/address_edit_before', p);
我们定义了一个apiAddress方法这个方法有一个参数pp是我们请求接口时携带的参数对象而后调用了我们封装的post方法post方法的第一个参数是我们的接口地址第二个参数是apiAddress的p参数即请求接口时携带的参数对象最后通过export导出apiAddress

然后在我们的页面中可以这样调用我们的api接口

import { apiAddress } from '@/request/api';// 导入我们的api接口
export default {        
    name: 'Address',    
    created () {
        this.onLoad();
    },
    methods: {            
        // 获取数据            
        onLoad() {
            // 调用api接口,并且提供了两个参数                
            apiAddress({                    
                type: 0,                    
                sort: 1                
            }).then(res => {
                // 获取数据成功后的其他操作
                ………………                
            })            
        }        
    }
}
其他的api接口就在pai.js中继续往下面扩展就可以了友情提示为每个接口写好注释哦!!!

api接口管理的一个好处就是我们把api统一集中起来如果后期需要修改接口我们就直接在api.js中找到对应的修改就好了而不用去每一个页面查找我们的接口然后再修改会很麻烦关键是万一修改的量比较大就规格gg了还有就是如果直接在我们的业务代码修改接口一不小心还容易动到我们的业务代码造成不必要的麻烦

好了最后把完成的axios封装代码奉上

/**axios封装
 * 请求拦截、相应拦截、错误统一处理
 */
import axios from 'axios';import QS from 'qs';
import { Toast } from 'vant';
import store from '../store/index'

// 环境的切换
if (process.env.NODE_ENV == 'development') {    
    axios.defaults.baseURL = '/api';
} else if (process.env.NODE_ENV == 'debug') {    
    axios.defaults.baseURL = '';
} else if (process.env.NODE_ENV == 'production') {    
    axios.defaults.baseURL = 'http://api.123dailu.com/';
}

// 请求超时时间
axios.defaults.timeout = 10000;

// post请求头
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

// 请求拦截器
axios.interceptors.request.use(    
    config => {
        // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
        // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
        const token = store.state.token;        
        token && (config.headers.Authorization = token);        
        return config;    
    },    
    error => {        
        return Promise.error(error);    
    })

// 响应拦截器
axios.interceptors.response.use(    
    response => {        
        if (response.status === 200) {            
            return Promise.resolve(response);        
        } else {            
            return Promise.reject(response);        
        }    
    },
    // 服务器状态码不是200的情况    
    error => {        
        if (error.response.status) {            
            switch (error.response.status) {                
                // 401: 未登录                
                // 未登录则跳转登录页面,并携带当前页面的路径                
                // 在登录成功后返回当前页面,这一步需要在登录页操作。                
                case 401:                    
                    router.replace({                        
                        path: '/login',                        
                        query: { redirect: router.currentRoute.fullPath } 
                    });
                    break;
                // 403 token过期                
                // 登录过期对用户进行提示                
                // 清除本地token和清空vuex中token对象                
                // 跳转登录页面                
                case 403:                     
                    Toast({                        
                        message: '登录过期,请重新登录',                        
                        duration: 1000,                        
                        forbidClick: true                    
                    });                    
                    // 清除token                    
                    localStorage.removeItem('token');                    
                    store.commit('loginSuccess', null);                    
                    // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
                    setTimeout(() => {                        
                        router.replace({                            
                            path: '/login',                            
                            query: { 
                                redirect: router.currentRoute.fullPath 
                            }                        
                        });                    
                    }, 1000);                    
                    break; 
                // 404请求不存在                
                case 404:                    
                    Toast({                        
                        message: '网络请求不存在',                        
                        duration: 1500,                        
                        forbidClick: true                    
                    });                    
                break;                
                // 其他错误,直接抛出错误提示                
                default:                    
                    Toast({                        
                        message: error.response.data.message,                        
                        duration: 1500,                        
                        forbidClick: true                    
                    });            
            }            
            return Promise.reject(error.response);        
        }       
    }
);
/** 
 * get方法,对应get请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function get(url, params){    
    return new Promise((resolve, reject) =>{        
        axios.get(url, {            
            params: params        
        })        
        .then(res => {            
            resolve(res.data);        
        })        
        .catch(err => {            
            reject(err.data)        
        })    
    });
}
/** 
 * post方法,对应post请求 
 * @param {String} url [请求的url地址] 
 * @param {Object} params [请求时携带的参数] 
 */
export function post(url, params) {    
    return new Promise((resolve, reject) => {         
        axios.post(url, QS.stringify(params))        
        .then(res => {            
            resolve(res.data);        
        })        
        .catch(err => {            
            reject(err.data)        
        })    
    });
}
axios的封装根据需求的不同而不同
1.优化axios封装去掉之前的get和post

2.断网情况处理

3.更加模块化的api管理

4.接口域名有多个的情况

5.api挂载到vue.prototype上省去引入的步骤

http.js中axios封装的优化先直接贴代码

/**
 * axios封装
 * 请求拦截、响应拦截、错误统一处理
 */
import axios from 'axios';
import router from '../router';
import store from '../store/index';
import { Toast } from 'vant';

/** 
 * 提示函数 
 * 禁止点击蒙层、显示一秒后关闭
 */
const tip = msg => {    
    Toast({        
        message: msg,        
        duration: 1000,        
        forbidClick: true    
    });
}

/** 
 * 跳转登录页
 * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
 */
const toLogin = () => {
    router.replace({
        path: '/login',        
        query: {
            redirect: router.currentRoute.fullPath
        }
    });
}

/** 
 * 请求失败后的错误统一处理 
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status, other) => {
    // 状态码判断
    switch (status) {
        // 401: 未登录状态,跳转登录页
        case 401:
            toLogin();
            break;
        // 403 token过期
        // 清除token并跳转登录页
        case 403:
            tip('登录过期,请重新登录');
            localStorage.removeItem('token');
            store.commit('loginSuccess', null);
            setTimeout(() => {
                toLogin();
            }, 1000);
            break;
        // 404请求不存在
        case 404:
            tip('请求的资源不存在'); 
            break;
        default:
            console.log(other);   
        }}

// 创建axios实例
var instance = axios.create({    timeout: 1000 * 12});
// 设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/** 
 * 请求拦截器 
 * 每次请求前,如果存在token则在请求头中携带token 
 */ 
instance.interceptors.request.use(    
    config => {        
        // 登录流程控制中,根据本地是否存在token判断用户的登录情况        
        // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token        
        // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码        
        // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。        
        const token = store.state.token;        
        token && (config.headers.Authorization = token);        
        return config;    
    },    
    error => Promise.error(error))

// 响应拦截器
instance.interceptors.response.use(    
    // 请求成功
    res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),    
    // 请求失败
    error => {
        const { response } = error;
        if (response) {
            // 请求已发出,但是不在2xx的范围 
            errorHandle(response.status, response.data.message);
            return Promise.reject(response);
        } else {
            // 处理断网的情况
            // eg:请求超时或断网时,更新state的network状态
            // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
            // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
            if (!window.navigator.onLine) {
               store.commit('changeNetwork', false);
            } else {
                return Promise.reject(error);
            }
        }
    });

export default instance;
这个axios和之前的大同小异做了如下几点改变

1.去掉了之前get和post方法的封装通过创建一个axios实例然后export default方法导出这样使用起来更灵活一些

2.去掉了通过环境变量控制baseUrl的值考虑到接口会有多个不同域名的情况所以准备通过js变量来控制接口域名这点具体在api里会介绍

3.增加了请求超时即断网状态的处理说下思路当断网时通过更新vuex中network的状态来控制断网提示组件的显示隐藏断网提示一般会有重新加载数据的操作这步会在后面对应的地方介绍

4.公用函数进行抽出简化代码尽量保证单一职责原则

下面说下api这块考虑到一下需求

1.更加模块化

2.更方便多人开发有效减少解决命名冲突

3.处理接口域名有多个情况

这里这里呢新建了一个api文件夹里面有一个index.js和一个base.js以及多个根据模块划分的接口js文件index.js是一个api的出口base.js管理接口域名其他js则用来管理各个模块的接口

先放index.js代码

/** 
 * api接口的统一出口
 */
// 文章模块接口
import article from '@/api/article';
// 其他模块的接口……

// 导出接口
export default {    
    article,
    // ……
}
index.js是一个api接口的出口这样就可以把api接口根据功能划分为多个模块利于多人协作开发比如一个人只负责一个模块的开发等还能方便每个模块中接口的命名哦

base.js:

/**
 * 接口域名的管理
 */
const base = {    
    sq: 'https://xxxx111111.com/api/v1',    
    bd: 'http://xxxxx22222.com/api'
}

export default base;
通过base.js来管理我们的接口域名不管有多少个都可以通过这里进行接口的定义即使修改起来也是很方便的

最后就是接口模块的说明例如上面的article.js:

/**
 * article模块接口列表
 */

import base from './base'; // 导入接口域名列表
import axios from '@/utils/http'; // 导入http中创建的axios实例
import qs from 'qs'; // 根据需求是否导入qs模块

const article = {    
    // 新闻列表    
    articleList () {        
        return axios.get(`${base.sq}/topics`);    
    },    
    // 新闻详情,演示    
    articleDetail (id, params) {        
        return axios.get(`${base.sq}/topic/${id}`, {            
            params: params        
        });    
    },
    // post提交    
    login (params) {        
        return axios.post(`${base.sq}/accesstoken`, qs.stringify(params));    
    }
    // 其他接口…………
}

export default article;
1.通过直接引入我们封装好的axios实例然后定义接口调用axios实例并返回可以更灵活的使用axios比如你可以对post请求时提交的数据进行一个qs序列化的处理等

2.请求的配置更灵活你可以针对某个需求进行一个不同的配置关于配置的优先级axios文档说的很清楚这个顺序是 lib/defaults.js 找到的库的默认值然后是实例的 defaults 属性最后是请求的 config 参数后者将优先于前者

3.restful风格的接口也可以通过这种方式灵活的设置api接口地址

最后为了方便api的调用我们需要将其挂载到vue的原型上在main.js中

import Vue from 'vue'
import App from './App'
import router from './router' // 导入路由文件
import store from './store' // 导入vuex文件
import api from './api' // 导入api接口

Vue.prototype.$api = api; // 将api挂载到vue的原型上
然后我们可以在页面中这样调用接口eg

methods: {    
    onLoad(id) {      
        this.$api.article.articleDetail(id, {        
            api: 123      
        }).then(res=> {
            // 执行某些操作      
        })    
    }  
}
再提一下断网的处理这里只做一个简单的示例

<template>  
    <div id="app">    
        <div v-if="!network">      
            <h3>我没网了</h3>      
            <div @click="onRefresh">刷新</div>      
        </div>    
        <router-view/>      
    </div>
</template>

<script>
    import { mapState } from 'vuex';
    export default {  
        name: 'App',  
        computed: {    
            ...mapState(['network'])  
        },  
        methods: {    
            // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
            onRefresh () {      
                this.$router.replace('/refresh')    
            }  
        }
    }
</script>
这是app.vue,这里简单演示一下断网。在http.js中介绍了,我们会在断网的时候,来更新vue中network的状态,那么这里我们根据network的状态来判断是否需要加载这个断网组件。断网情况下,加载断网组件,不加载对应页面的组件。当点击刷新的时候,我们通过跳转refesh页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。


// refresh.vue
beforeRouteEnter (to, from, next) {
    next(vm => {            
        vm.$router.replace(from.fullPath)        
    })    
}

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
前端工具2024-05-08
以Babel为例,讲述插件的机制
Babel 是一个 JavaScript 编译器 Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情: 语法转换 通过 Polyfill 方式在目标环境中添加缺失的功能(通过引入第三方 polyfill 模块,例如 ) 源码转换(codemods) Babel 和...
学习
前端工具2024-05-07
实现自定义脚手架
首先,每次创建项目后都需要很多重复性操作,比如安装UI库、css预处理器。在业务层面,每次也要添加一些必备的功能,比如登录、404页面等。其次,可以锻炼自己的技术。 脚手架创建项目的流程: 1. 让用户选择模板; 2. 根据用户输入填充模板; 3. 下载模板文件到创建的新文件夹中; 从框架来看,创建项目的时候需要选择是vue框架还是react框架,以及是否需要添加 typescript。 从插件来看,需要安装UI库、CSS预处理器、路由...
学习
前端工具2024-05-07
搭建前端脚手架
需求: 公司里前端初始项目有多个比如: 管理系统、electron、h5... 每次新项目都需要去找仓库里对应的基建代码?于是准备自搭一个脚手架,实现新项目时直接使用脚手架创建对应项目的模板。 采用monorepo的模式来管理脚手架、在这里只提供核心代码实现,废话不多说开干!开干! 1、初始化项目 2、修改package内容 3、使用第三方包解析交互命令 使用prompts来解析命令,实现目标如通过 创建项目,如图: 在 文件中 使用...
学习
前端工具2023-12-27
汇总前端脚手架开发中常用的第三方库
在前端工程化方面我们使用的各种工具,绝大部分都是使用 写的。 比如,打包工具 webpack 及 rollup;前端框架 Vue 自带的脚手架工具 vue cli;以及 CSS 样式工具scss、less等等。 在日常工作中难免会遇到一些需求,需要自己创建一个脚手架工具来提升整个团队的研发部署效率。 目前,npm上汇集了大量优秀的开发脚手架的第三库,熟练掌握这些库,可以大大提升我们的开发效率。本文将介绍一些在开发前端脚手架方面非常常用的...
学习
前端工具2023-09-21
面试官:”Vite为什么快?“
面试官:”Vite为什么快?“ 阅读8分钟 前面一篇文章介绍了如何做 Vite 的插件开发 ,这篇文章介绍清晰 Vite 的构建原理,为什么比 Webpack 构建速度快这么多。 1\. 一句话解释 Webpack 的构建原理 前端之所有需要 类似于 Webpack 这样的构建工具,是为了提高项目的开发效率,Webpack 通过分析js中的 require 语句,分析出当前 js 文件所有的依赖文件,通过递归的方式层层分析后,得到整个项...
学习
前端工具2023-02-10
前端测试之Jest深入浅出
1\. 为什么要做前端测试 首先,我认为前端测试并不是所有项目都必须的,因为写测试代码是需要要花费一定时间的,当项目比较简单的时候,花时间写测试代码可能反而会影响开发效率,但是需要指出的是,我们前端开发过程中,编写测试代码,有以下这些好处: 1. 更快的发现bug,让绝大多数bug在开发阶段发现解决,提高产品质量 2. 比起写注释,单元测试可能是更好的选择,通过运行测试代码,观察输入和输出,有时会比注释更能让别人理解你的代码(当然,重要...

评论

请登录后发表评论

去登录
加载评论中...