1. Axios encapsulation

In Vue projects, we usually use the AXIos library, which is a Promise-based HTTP library that runs in both the browser and Node.js. It has many excellent features, such as request and response interception, request cancellation, JSON conversion, client-side XSRF defense, and more. So our company has abandoned the maintenance of its official vue_resource library and recommended the AXIos library instead. If you’re not familiar with Axios, you can refer to the Axios documentation

The installation

npm install axios; / / install axiosCopy the code

The introduction of

I usually create a request folder in the project’s SRC directory, and then create an http.js and an api.js file inside. The http.js file is used to wrap our AXIos, and the api.js file is used to unify our interfaces.

// Introduce axios in http.js
import axios from 'axios'; / / introduce axios
import QS from 'qs'; // Introduce the QS module, which is used to serialize post data, as described later
// Vant toast prompt box component, you can change according to your own UI component
import {Toast} from 'vant'
Copy the code

Environment switch

Our project environment may have a development environment, a test environment, and a production environment. We use node’s environment variables to match our default interface URL prefix. Axios.defaults. baseURL can set axios’ default request address without further details.

// Environment switch
if (process.env.NODE_ENV === 'development') {
    axios.defaults.baseURL = 'http://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'
}
Copy the code

Setting request Timeout

Set the default request timeout with axios.defaults.timeout. For example, if the request exceeds 10s, the system notifies the user that the request has timed out and needs to be refreshed.

axios.defaults.timeout = 10000;
Copy the code

Setting of the POST request header

For post requests, we need a header, so we can set the default header for post to Application/X-www-form-urlencoded; charset=UTF-8

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
Copy the code
  • Request to intercept

We can intercept a request before we send it, why intercept, what do we intercept a request for? For example, some requests require users to log in before they can access them, or for POST requests, we need to serialize our markup data. At this point, we can do what we want by intercepting the request before it is sent.

Request to intercept

// Import the vuex first, because we need to use the state object inside
// The vuex path is written according to its own path;
import store from '@/store/index'

// Request interceptor
axios.interceptors.request.use(    
    config= > {        
        // Check whether the token exists in the VUEX before each request is sent
        // If yes, add a token to the header of the HTTP request, so that the background can determine your login status according to the token
        // Even if the token exists locally, it is possible that the token is expired, so the return status is determined in the response interceptor
        const token = store.state.token;        
        token && (config.headers.Authorization = token);        
        return config;    
    },    
    error => {        
        return Promise.error(error);    
})
Copy the code

Generally, after login, the user’s token is stored locally through localStorage or cookie. Then when the user enters the page (i.e. in main.js), the user will first read the token from the localStorage. If the token exists, the user has logged in. Then update the token status in the VUEX. Then, when you request the interface at a lower level, you will carry the token in the header of the request, and the background staff can judge whether your login is expired according to the token you carry. If you do not carry the token, it means that you have not logged in. Each request carries a token, so what if a page can be accessed without user login? In fact, your front-end request can carry token, but the background can choose not to accept ah!

Response interception

// Responder interception
axios.interceptors.response.use(    
    response= > {   
        // If the returned status code is 200, the interface request succeeds and data can be obtained normally
        // Otherwise, an error is thrown
        if (response.status === 200) {            
            return Promise.resolve(response);        
        } else {            
            return Promise.reject(response); }},// The server status code does not start with 2
    // Here you can negotiate a unified error status code with your backend developers
    // Then perform some operations according to the returned status code, such as login expiration message, error message and so on
    // A few common operations are listed below, and other requirements can be expanded
    error => {            
        if (error.response.status) {            
            switch (error.response.status) {                
                // 401: Not logged in
                // If you do not log in, the login page is displayed and the path of the current page is displayed
                // Return to the current page after successful login. This step must be performed on the login page.
                case 401:                    
                    router.replace({                        
                        path: '/login'.query: { 
                            redirect: router.currentRoute.fullPath 
                        }
                    });
                    break;

                // 403 Token expired
                // Prompts the user when the login expires
                // Clear local tokens and vuEX token objects
                // Go to the login page
                case 403:
                     Toast({
                        message: 'Login expired, please log in again'.duration: 1000.forbidClick: true
                    });
                    / / remove the token
                    localStorage.removeItem('token');
                    store.commit('loginSuccess'.null);
                    // Jump to the login page and upload the fullPath of the page to be accessed. After successful login, jump to the page to be accessed
                    setTimeout((a)= > {                        
                        router.replace({                            
                            path: '/login'.query: { 
                                redirect: router.currentRoute.fullPath 
                            }                        
                        });                    
                    }, 1000);                    
                    break; 

                // 404 request does not exist
                case 404:
                    Toast({
                        message: 'Network request does not exist'.duration: 1500.forbidClick: true
                    });
                    break;
                // Other errors, directly throw error message
                default:
                    Toast({
                        message: error.response.data.message,
                        duration: 1500.forbidClick: true
                    });
            }
            return Promise.reject(error.response); }}});Copy the code

A response interceptor is the data that the server sends back to us and we can do something with 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 type of the status code of the error. In fact, this is mainly an operation of unified processing of errors and adjusting the login page after no login or expiration of the login.

Note that the Toast() method above is the Toast light prompt component in the Vant library that I introduced, and you use one of your prompt components, depending on your UI library.

Encapsulate get and POST methods

Common Ajax request methods include get, POST, PUT and so on. Axios has a lot of similar methods, but if you don’t know, check out the documentation. But to simplify our code, we’ll do a simple wrapper. Here we mainly encapsulate two methods: GET and POST.

Get method: We do this by defining a get function that takes two parameters. The first parameter is the URL 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 a value when axios’s request succeeds, while the reject server returns a value when the request fails. Finally, the get function is thrown through export

/** * get method, corresponding to get request * @param {String} url [request URL] * @param {Object} params [request parameters] */
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) }) }); }Copy the code

Post: The principle is basically the same as get, but note that the POST method must use the operation to serialize the submit from the parameter object, so here we serialize our parameters through the Qs module of Node. This is important because without serialization, the background can’t get the data you submitted. This is the beginning of the article we import QS from ‘QS ‘; The reason why. If you don’t know what serialization means, just Google it and you’ll get a lot of answers.

/** * Post request * @param {String} url [requested URL] * @param {Object} params [requested parameters] */
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)
        })
    });
}
Copy the code

One small detail here is that the axios.get() method differs from axios.post() in the way arguments are written when submitting data. The difference is that the second argument to get is a {}, and the object’s params property value is a parameter object’s. The second argument to POST is a parameter object. The difference between the two should pay attention to oh!

Axios encapsulation is almost complete, so let’s briefly talk about unified API management

A neat API is like a circuit board, no matter how complex it is, it can clear the entire circuit. As mentioned above, we will create a new api.js file and store all our API interfaces in the entire file.

  • First we introduce our wrapped GET and POST methods in api.js
/** * API interface unified management */
import { get, post } from './http'
Copy the code

Now, for example, we have an interface like this, which is a POST request:

http://www.baiodu.com/api/v1/users/my_address/address_edit_before
Copy the code

We can wrap it in api.js like this:

export const apiAddress = p= > post('api/v1/users/my_address/address_edit_before', p);
Copy the code

We define an apiAddress method that takes one parameter, p, which is the parameter object we carry when we request the interface. Our wrapped POST method is then called. The first argument to the POST method is our interface address, and the second argument is the p argument to the apiAddress, which is the parameter object that the interface is requested with. Finally, export the apiAddress

Then we can call our API from our page like this:

import { apiAddress } from '@/request/api'; // Import our APIexport default {        
    name: 'Address'.created() { this.onLoad(); }, methods: {// get dataonLoad() {// Call the API interface and provide two arguments, apiAddress({)type: 0, sort: 1}). Then (res = > {/ / get the data after the success of other operations........................... })}}}Copy the code

For other API interfaces, you can extend them further in api.js. Tips, write a good comment for each interface!!

One of the benefits of API interface management is that we centralized the API, so if we need to modify the interface later, we can directly find the corresponding change in api.js, instead of going to every page to find our interface and then modify it. The key is, in case the amount of modification is relatively large, with respect to specifications GG. In addition, if we directly modify the interface of our business code, it is easy to move our business code and cause unnecessary trouble.

Okay, finally, the finished AXIos wrapper code.

/**axios encapsulates request interception, corresponding interception, unified handling of errors */ import axios from'axios'; import QS from'qs';
import { Toast } from 'vant';
import store from '.. /store/index'// Environment switchif (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/'; } // Request timeout time axios.defaults.timeout = 10000; / / post request head axios. Defaults. Headers. Post ['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; / / request interceptor axios. Interceptors. Request. Use (config = > {/ / every time before sending a request to judge the existence of a token, if present, are unified in the HTTP request header will add token, Const token = store.state.token; const token = store.state.token; const token = store.state. token && (config.headers.Authorization = token);return config;    
    },    
    error => {        
        returnPromise.error(error); }) / / response interceptor axios. Interceptors. Response. Use (response = > {if (response.status === 200) {            
            return Promise.resolve(response);        
        } else {            
            returnPromise.reject(response); }}, // the server status code is not 200 error => {if(error.response.status) { switch (error.response.status) { // 401: If you do not log in // If you do not log in, the login page is displayed and the path of the current page is displayed. // If you successfully log in to the current page, you need to perform this step on the login page.case 401:                    
                    router.replace({                        
                        path: '/login',                        
                        query: { redirect: router.currentRoute.fullPath } 
                    });
                    break; // 403 Token expiration // The login expiration prompts the user. // Clear the local token and the token object in the VUEX. // The login page is displayedcase 403:                     
                    Toast({                        
                        message: 'Login expired, please log in again',                        
                        duration: 1000,                        
                        forbidClick: true}); / / remove the tokenlocalStorage.removeItem('token');                    
                    store.commit('loginSuccess', null); // Jump to the login page and upload the fullPath of the page to be accessed. After successful login, jump to the page to be accessedsetTimeout(() => {                        
                        router.replace({                            
                            path: '/login',                            
                            query: { 
                                redirect: router.currentRoute.fullPath 
                            }                        
                        });                    
                    }, 1000);                    
                    break; // 404 request does not existcase 404:                    
                    Toast({                        
                        message: 'Network request does not exist',                        
                        duration: 1500,                        
                        forbidClick: true                    
                    });                    
                break; / / other errors, directly thrown error default: Toast ({message: error. The response. The data message, duration: 1500, forbidClick:true                    
                    });            
            }            
            returnPromise.reject(error.response); }}); /** * get method, corresponding to get request * @param {String} url [request URL] * @param {Object} params [request parameters] */export function get(url, params){    
    returnnew Promise((resolve, reject) =>{ axios.get(url, { params: params }) .then(res => { resolve(res.data); }) .catch(err => { reject(err.data) }) }); } /** * post method, corresponding to the post request * @param {String} url [request URL] * @param {Object} params [request parameters] */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)        
        })    
    });
}

Copy the code

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

update

The encapsulation of AXIos varies depending on your requirements. I would like to thank some colleagues for their pertinent suggestions. I have also thought about them and made improvements according to different needs. The main changes are as follows:

1. Optimize the AXIos wrapper to remove the previous GET and POST (1, 2 in the same code block)

2. Treatment of disconnection (1, 2 in the same code block)

Optimizations for AXIos encapsulation in HTTP.js:

/** * axios encapsulation * request interception, response interception, error unified handling */ 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}); } /** * jump to the login page */ const toLogin = () => {router.replace({path:'/login', query: { redirect: router.currentRoute.fullPath } }); } @param {Number} status Status of a failed request */ const errorHandle = (status, Other) => {// Status code Switch (status) {// 401: Not logged in, the login page is displayedcase 401:
            toLogin();
            break; // 403 Token expired // Clear the token and jump to the login pagecase 403:
            tip('Login expired, please log in again');
            localStorage.removeItem('token');
            store.commit('loginSuccess', null);
            setTimeout(() => {
                toLogin();
            }, 1000);
            break; // 404 request does not existcase 404:
            tip('Requested resource does not exist'); 
            break; default: console.log(other); Var instance = axios.create({timeout: 1000 * 12}); / / set the post request header instance. Defaults. Headers. Post ['Content-Type'] = 'application/x-www-form-urlencoded'; / * * * request interceptor * before every request, if there is a token is carried in the request header token * / instance. The interceptors. Request. Use (config = > {/ / login process control, Check the login status of the user according to whether the local token exists // Even if the token exists, the token may be expired, so the request header carries the token. // The background checks the login status of the user according to the token carried. And gives us the corresponding status code // and then we can do some uniform operations based on the status code in the response interceptor. const token = store.state.token; token && (config.headers.Authorization = token);returnconfig; }, Error = > Promise. The error (error)) / / response interceptor instance. The interceptors. Response. Use (a/res/request success = > res. Status = = = 200? Promise.resolve(res) : promise.reject (res), error => {const {response} = error;ifErrorHandle (response.status, response.data.message) {// The request has been sent, but it is not in the scope of 2xx.return Promise.reject(response);
        } else{// Handle the disconnection situation // eg: Update the network state of state when the request times out or disconnection. // Network state controls the display of a global disconnection prompt component in app.vue. // About the refresh of the disconnection component to retrieve data, will be explained in the disconnection componentif(! window.navigator.onLine) { store.commit('changeNetwork'.false);
            } else {
                returnPromise.reject(error); }}});export default instance;
Copy the code

This axios is much the same as the previous one, with the following changes:

1. Remove the wrap of get and POST methods, create an axiOS instance and export the default method everywhere, so that it is more flexible to use.

2. The value of baseUrl controlled by environment variables is removed. Considering that the interface can have multiple different domain names, you are prepared to control the interface domain name through the JS variable. We’ll talk about that in the API.

3. Added the processing of request timeout, that is, disconnection status. When the network is disconnected, the display and hiding of the disconnection prompt component can be controlled by updating the status of the network in VUEX. The operation of reloading data is usually performed when the network is disconnected. This step will be described in the following section.

4. Public functions are extracted to simplify the code and ensure the principle of single responsibility as far as possible.

3. More modular API management

Let’s talk about the API section, considering the following requirements:

1. Be more modular

2. More convenient for multi-person development, effectively reduce the resolution of naming conflicts

3. Handle multiple interface domain names

There is a new API folder with an index.js and a base.js, as well as several interface JS files divided by modules. Index.js is an API exit, base.js manages the interface domain name, and other JS manage the interface of each module.

Let’s start with index.js:

/ * * * the unification of the API interface * / export/import/article module interface article the from'@/api/article'; // Interface of other modules...... // Export interfaceexportDefault {article, //...... }Copy the code

Index.js is an API interface exit, so that the API interface can be divided into multiple modules according to the function, which is conducive to collaborative development of many people, such as one person is only responsible for the development of a module, etc., can also facilitate the naming of the interface in each module.

4. Multiple interface domain names exist

base.js:

/** ** const base = {sq:'https://xxxx111111.com/api/v1',    
    bd: 'http://xxxxx22222.com/api'
}

export default base;
Copy the code

Manage our interface domain name through base.js, no matter how many interface can be defined through this. Even if it is modified, it is very convenient.

Finally, a description of interface modules, such as article. Js above:

/** * article module interface list */ import base from'./base'; // Import interface domain names import axios from'@/utils/http'; // Import qs from axios instance created in HTTP'qs'; // Whether to import qs module const article = {// News listarticleList () {        
        return axios.get(`${base.sq}/topics`); }, // articleDetail (id, params) {return axios.get(`${base.sq}/topic/${id}`, { params: params }); }, // post submit login (params) {return axios.post(`${base.sq}/accesstoken`, qs.stringify(params)); } // Other interfaces ………… }export default article;
Copy the code

1. You can use AXIOS more flexibly by importing our wrapped AXIOS instance directly, then defining the interface, calling the AXIOS instance and returning it. For example, you can do a QS serialization of the data submitted during a POST request, etc.

2. Request configuration is more flexible, you can make a different configuration for a particular requirement. The axios documentation is clear about the precedence of the configuration: the library defaults found in lib/defaults.js, then the instance defaults properties, and finally the request config parameters. The latter will take precedence over the former.

3. API addresses can also be flexibly set in this way for restful interfaces.

5. The API hangs on vue.prototype without the added steps

Finally, to facilitate API calls, we need to mount it on the vue prototype. In the main. In js:

import Vue from 'vue'
import App from './App'
import router from './router'// Import the routing file import store from'./store'// Import the vuex file import API from'./api' // 导入api接口

Vue.prototype.$api= api; // Mount the API to the vue prototypeCopy the code

We can then call the interface in the page like this, eg:

methods: {    
    onLoad(id) {      
        this.$api.article.articledetail (id, {API: 123}). Then (res=> {// perform some operations})}}Copy the code

Here is a simple example of disconnection:

<template>  
    <div id="app">    
        <div v-if=! "" network"</h3> <div @click="onRefresh"> Refresh </div> </div> <router-view/> </div> </template> <script> import {mapState} from'vuex';
    export default {  
        name: 'App', computed: { ... mapState(['network'}, methods: {// Refresh the current page data by jumping to an empty page and backonRefresh () {      
                this.$router.replace('/refresh')    
            }  
        }
    }
</script>
Copy the code

This is app.vue, a quick demonstration of disconnection. As introduced in HTTP. js, we will update the state of the network in VUE when the network is disconnected, so we judge whether to load the disconnected component according to the state of the network. When the network is disconnected, load the disconnected component, but not the component on the corresponding page. When we hit Refresh, we retrieve the data by jumping to the refesh page and immediately returning. So we need to create a refresh. Vue page and return to the current page in its beforeRouteEnter hook.

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

This is a universal disconnection prompt, but can also be done according to your own project requirements. How it works is a matter of opinion.

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

If it helps you, collect ❤❤! (This article is borrowed from others and made this personal record)