Encapsulation of Axios in vue and management of API interface (update)


As shown in the picture, you are facing a mess of code~~~ I really want to say, What F~U~C~K! ! !

Returning to the topic, what we are talking about is the encapsulation of axios and the unified management of the api interface. In fact, the main purpose is to help us simplify the code and facilitate later update and maintenance.

First, the package of axios

In the vue project, we usually use the axios library to interact with the background to obtain data, which is a promise-based http library that can run on the browser side and node.js. It has many excellent features, such as intercepting requests and responses, canceling requests, converting json, client-side defense against XSRF, etc. Therefore, our Youda also decisively gave up the maintenance of its official library vue-resource, and directly recommended us to use the axios library. If you still don't know axios, you can move to the axios documentation .

Install

npm install axios; // 安装axios复制代码

introduce

Generally, I will create a new request folder in the src directory of the project, and then create a new http.js and an api.js file in it. The http.js file is used to encapsulate our axios, and the api.js is used to manage our interface uniformly.

// 在http.js中引入axios
import axios from 'axios'; // 引入axios
import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,后面会提到
// vant的toast提示框组件,大家可根据自己的ui组件更改。
import { Toast } from 'vant'; 
复制代码

environment switching

Our project environment may have development environment, test environment and production environment. We use node's environment variables to match our default interface url prefix. axios.defaults.baseURL can set the default request address of axios. Not much to say.

// 环境的切换
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';
}复制代码

set request timeout

Set the default request timeout through axios.defaults.timeout. For example, if it exceeds 10s, the user will be informed that the current request timed out, please refresh, etc.

axios.defaults.timeout = 10000;复制代码

post request header settings

When making a post request, we need to add a request header, so we can make a default setting here, that is, set the request header of the post toapplication/x-www-form-urlencoded;charset=UTF-8

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';复制代码
  • request interception

We can intercept a request before sending a request. Why do we intercept it? What do we use to intercept the request? For example, some requests can only be accessed after the user is logged in, or when making a post request, we need to serialize the data we submit. At this time, we can perform an interception before the request is sent, so as to do what we want.

request interception

// 先导入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);    
})
复制代码

Let's talk about the token here. Generally, after the login is completed, the user's token is stored locally through localStorage or cookie, and then every time the user enters the page (ie in main.js), the token will be read from the local storage first. , if the token exists, it means that the user has logged in, then update the token status in vuex. Then, every time you request the interface, the token will be carried in the header of the request, and the background staff can judge whether your login has expired according to the token you carry. If it is not carried, it means that you have not logged in. At this time, some friends may have doubts, that is, each request carries a token, so what if a page can be accessed without user login? In fact, your front-end request can carry a token, but the back-end can choose not to receive it!

Response interception

// 响应拦截器
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);
        }
    }    
});复制代码

The response interceptor is well understood, that is, the data returned by the server to us, we can do some processing on it before we get it. For example, the above idea: if the status code returned by the background is 200, the data will be returned normally, otherwise, some errors we need will be made according to the wrong status code type. An action to adjust the login page afterward.

It should be noted that the Toast() method above is the toast light prompt component in the vant library that I introduced. You can use one of your prompt components according to your ui library.

Encapsulate get method and post method

Our commonly used ajax request methods include get, post, put and other methods, I believe that little friends will not be unfamiliar. There are many similar methods corresponding to axios. If you are not clear, you can read the documentation. But in order to simplify our code, we still have to do a simple encapsulation of it. Below we mainly encapsulate two methods: get and post.

get method : We define a get function. The get function has two parameters. The first parameter represents the url address we want to request, and the second parameter is the request parameter we want to carry. The get function returns a promise object, and the resolve server returns the value when the axios request succeeds, and rejects the error value when the request fails. Finally, the get function is thrown through export.

/**
 * 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 method: The principle is basically the same as get, but it should be noted that the post method must use the operation of serializing the submitted parameter object, so here we serialize our parameters through the qs module of node. This is very important. If there is no serialization operation, the background will not be able to get the data you submitted. This is why we import QS from 'qs'; at the beginning of the article. If you don't understand what serialization means, just look it up on Baidu, there are a lot of answers.

/** 
 * 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)
        })
    });
}复制代码

Here is a small detail, there is still a difference between the axios.get()method and axios.post()the way of writing parameters when submitting data. The difference is that the second parameter of get is a {}, and then the value of the params property of this object is a parameter object. The second parameter of post is a parameter object. Note the slight difference between the two!



The encapsulation of axios is basically completed. Let's briefly talk about the unified management of api.

A neat api is like a circuit board, even if it is complex, the whole circuit is very clear. As mentioned above, we will create a new api.js, and then store all our api interfaces in this file.

  • First, we introduce our encapsulated get and post methods in api.js

/**   
 * api接口统一管理
 */
import { get, post } from './http'复制代码

Now, for example, we have such an interface, which is a post request:

http://www.baiodu.com/api/v1/users/my_address/address_edit_before复制代码

We can encapsulate it like this in api.js:

export const apiAddress = p => post('api/v1/users/my_address/address_edit_before', p);复制代码

We define a apiAddressmethod, this method has a parameter p, p is the parameter object we carry when we request the interface. postThen the method we encapsulated is called post. The first parameter of the method is our interface address, and the second parameter is apiAddressthe p parameter, which is the parameter object carried when requesting the interface. Finally export through export apiAddress.

Then in our page we can call our api interface like this:

import { apiAddress } from '@/request/api';// 导入我们的api接口
export default {        
    name: 'Address',    
    created () {
        this.onLoad();
    },
    methods: {            
        // 获取数据            
        onLoad() {
            // 调用api接口,并且提供了两个参数                
            apiAddress({                    
                type: 0,                    
                sort: 1                
            }).then(res => {
                // 获取数据成功后的其他操作
                ………………                
            })            
        }        
    }
}复制代码

For other api interfaces, you can continue to expand below in pai.js. Friendly reminder, write notes for each interface! ! !

One advantage of api interface management is that we centralize the api. If we need to modify the interface later, we can directly find the corresponding modification in api.js instead of going to each page to find our interface and then modify it. will be troublesome. The point is, if the amount of modification is relatively large, the specification is gg. In addition, if we modify the interface directly in our business code, it is easy to move our business code accidentally and cause unnecessary trouble.


Well, finally, the completed axios package code is presented.

/**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)        
        })    
    });
}
复制代码


If you like it, give it a ❤❤(*^▽^*)


*********Gorgeous dividing line******************Gorgeous dividing line************* *****Gorgeous dividing line******************Gorgeous dividing line***************** *Gorgeous dividing line*********

2018.8.14 update

The package of axios varies according to different needs. Thank you very much for some very pertinent suggestions in the comments, I have also thought about it and improved it for different needs. The main changes are as follows:

1. Optimize the axios package, remove the previous get and post

2. Handling of network disconnection

3. More modular api management

4. There are multiple interface domain names

5. The api is mounted on vue.prototype to save the introduction steps


To optimize the axios package in http.js, first paste the code directly:

/**
 * 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中控制着一个全局的断网提示组件的显示隐藏
            // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
            store.commit('changeNetwork', false);
        }
    });

export default instance;复制代码
This axios is similar to the previous one, with the following changes:

1. The encapsulation of the previous get and post methods is removed, and it is more flexible to use by creating an axios instance and then exporting the default method.

2. Removed the value of controlling baseUrl through environment variables. Considering that the interface will have multiple different domain names, it is prepared to control the interface domain name through js variables. This will be described in detail in the api.

3. Added request timeout, that is, the processing of disconnected network status. Let's talk about the idea. When the network is disconnected, the display and hiding of the disconnection prompt component are controlled by updating the state of the network in vuex. The disconnection prompt generally has the operation of reloading the data. This step will be introduced in the corresponding place later.

4. Extract public functions, simplify the code, and try to ensure the principle of single responsibility.


Let's talk about the API, considering the requirements:

1. More modular

2. It is more convenient for multiple people to develop, effectively reducing the resolution of naming conflicts

3. There are multiple situations in handling interface domain names

Here, a new api folder is created, which contains an index.js and a base.js, as well as multiple interface js files divided according to modules. index.js is an api export, base.js manages the interface domain name, and other js are used to manage the interfaces of each module.

Put the index.js code first:

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

// 导出接口
export default {    
    article,
    // ……
}复制代码

index.js is the export of an api interface, so that the api interface can be divided into multiple modules according to functions, which is conducive to multi-person collaborative development. For example, one person is only responsible for the development of one module, etc., and it is also convenient for the interface in each module. Oh name it.

base.js:

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

export default base;复制代码

Through base.js to manage our interface domain name, no matter how many, we can define the interface here. Even if it is modified, it is very convenient.

The last is the description of the interface module, such as the above 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. By directly introducing our encapsulated axios instance, then defining the interface, calling the axios instance and returning it, you can use axios more flexibly. For example, you can perform a qs serialization process on the data submitted during the post request.

2. The configuration of the request is more flexible, you can make a different configuration for a certain requirement. Regarding the priority of the configuration, the axios documentation says it very clearly, the order is: the default value of the library lib/defaults.jsfound , then the defaultsattribute of the instance, and finally the configparameters . The latter will take precedence over the former.

3. Restful-style interface, you can also flexibly set the api interface address in this way.

Finally, in order to facilitate the call of the api, we need to mount it on the prototype of vue. In 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的原型上复制代码

Then we can call the interface like this in the page, eg:

methods: {    
    onLoad(id) {      
        this.$api.article.articleDetail(id, {        
            api: 123      
        }).then(res=> {
            // 执行某些操作      
        })    
    }  
}复制代码

Let's talk about the handling of network disconnection, here is just a simple example:

<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>复制代码

This is app.vue, here is a simple demonstration of disconnection. Introduced in http.js, we will update the state of the network in vue when the network is disconnected, so here we judge whether the disconnected component needs to be loaded according to the state of the network. When the network is disconnected, the disconnected components are loaded, and the components of the corresponding page are not loaded. When we click to refresh, we achieve the operation of re-fetching data by jumping to the refresh page and then returning immediately. Therefore, we need to create a new refresh.vue page and beforeRouteEnterreturn to the current page in its hook.

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

This is a global disconnection prompt. Of course, it can also be operated according to your own project needs. The benevolent sees the benevolent and the wise sees the wisdom.

If there are more needs, or different needs, you can make an improvement according to your own needs.


If you feel it is helpful to you, then bookmark ❤❤!


Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324052361&siteId=291194637
Recommended