catalogue
Setting of post request header
Encapsulate get method and post method
1, Packaging of axios
In the vue project, we usually use the axios library to interact with the background to obtain data. It is an http Library Based on promise, which can run on the browser and node JS. It has many excellent features, such as intercepting requests and responses, canceling requests, transforming json, client defense cSRF, etc. Therefore, our Youda also resolutely 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 about axios, you can move axios documentation.
install
npm install axios; // Install axios replication code
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. http.js file is used to encapsulate our axios, API JS is used to uniformly manage our interface.
// At http Introducing axios into JS import axios from 'axios'; // Introducing axios import QS from 'qs'; // The qs module is introduced to serialize post type data, which will be mentioned later // The toast prompt box component of vant can be changed according to your own ui components. import { Toast } from 'vant'; Copy code
Environment switching
Our project environment may include development environment, test environment and production environment. We use the node environment variable to match our default interface url prefix. axios.defaults.baseURL can set the default request address of Axios, so I won't say much.
// Environment switching 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'; }Copy code
Set request timeout
Via Axios defaults. Timeout sets the default request timeout. For example, if it exceeds 10s, the user will be informed of the current request timeout, please refresh, etc.
axios.defaults.timeout = 10000;Copy code
Setting of post request header
When making a post request, we need to add a request header, so we can make a default setting here, that is, set the post request header to application/x-www-form-urlencoded;charset=UTF-8
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';Copy code
- Request interception
We can intercept a request before sending a request. Why should we intercept it? What are we using to intercept a request? For example, some requests can only be accessed after the user logs in, or when a post request is made, we need to serialize the data we submit. At this time, we can intercept the request before it is sent, so as to carry out the operation we want.
Request interception
// First import vuex, because we need to use the state object inside // The path of vuex is written according to its own path import store from '@/store/index'; // Request interceptor Axios interceptors. request. use( config => { // Judge whether there is a token in vuex before sending a request each time // If it exists, a token will be added to the header of the http request, so that the background can judge your login according to the token // Even if there is a token locally, it may be expired, so the return status should be judged in the response interceptor const token = store.state.token; token && (config.headers.Authorization = token); return config; }, error => { return Promise.error(error); }) Copy code
Let's talk about the token here. Generally, after logging in, the user's token is stored locally through localStorage or cookie. Then every time the user enters the page (i.e. in main.js), he will first read the token from the local storage. If the token exists, it means that the user has logged in, then the token status in vuex will be updated. Then, every time you request an interface, you will carry a token in the header of the request. The background personnel can judge whether your login has expired according to the token you carry. If you don't carry it, it means you haven't logged in. At this time, some small partners may have questions, that is, each request carries a token. What if a page can be accessed without user login? In fact, your front-end request can carry a token, but the background can choose not to receive it!
Interception of response
// Response interceptor axios.interceptors.response.use( response => { // If the returned status code is 200, it indicates that the interface request is successful and the 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 background developers // Then perform some operations according to the returned status code, such as login expiration prompt, error prompt, etc // Several common operations are listed below, and other requirements can be expanded by themselves error => { if (error.response.status) { switch (error.response.status) { // 401: not logged in // If you are not logged in, you will jump to the login page and carry the path of the current page // After successful login, return to the current page. This step needs to be operated on the login page. case 401: router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); break; // 403 token expired // Prompt users when login expires // Clear local token and empty token object in vuex // Jump to login page case 403: Toast({ message: 'Login expired, please login again', duration: 1000, forbidClick: true }); // Clear token localStorage.removeItem('token'); store.commit('loginSuccess', null); // Jump to the login page and pass the fullPath of the page to be viewed. After successful login, jump to the page to be visited setTimeout(() => { router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); }, 1000); break; // 404 request does not exist case 404: Toast({ message: 'The network request does not exist', duration: 1500, forbidClick: true }); break; // For other errors, directly throw the error prompt default: Toast({ message: error.response.data.message, duration: 1500, forbidClick: true }); } return Promise.reject(error.response); } } });Copy code
The response interceptor is well understood. It is the data returned by the server. We can process it before we get it. For example, the above idea: if the background return state code is 200, then it returns data normally, otherwise, we need to make some mistakes according to the wrong status code type. In fact, this is mainly an error unified processing and an operation that is not logon or logout after expiration.
It should be noted that the Toast() method above is the toast light prompt component in the vant library I introduced. You can use one of your prompt components according to your ui library.
Encapsulate get method and post method
Our common ajax request methods include get, post, put and so on. I'm sure our little friends will be familiar with them. There are many similar methods corresponding to axios. If you are unclear, you can see the documents. However, in order to simplify our code, we still need a simple encapsulation. Next, 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. When the axios request succeeds, the resolve server returns the value, and when the request fails, the reject error value. Finally, throw the get function through export.
/** * get Method, corresponding to get request * @param {String} url [url address of the request] * @param {Object} params [Parameters carried during request] */ 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 code
Post method: the principle is basically the same as that of get, but it should be noted that the post method must use the operation of serializing the submission from the 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 get the data you submitted. This is the beginning of the article. We import QS from 'qs'; The reason for this. If you don't understand what serialization means, just Baidu. There are a lot of answers.
/** * post Method, corresponding to the post request * @param {String} url [url address of the request] * @param {Object} params [Parameters carried during request] */ 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 code
Here's a little detail, Axios Get () method and Axios When post () submits data, the parameters are written differently. The difference is that the second parameter of get is a {}, and the params property value of this object is a parameter object. The second parameter of post is a parameter object. Pay attention to 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, which can make the whole circuit clear even if it is complex. As mentioned above, we will create an api JS, and then store all our api interfaces in this file.
- First, we're at API JS introduces our encapsulated get and post methods
/** * api Unified interface management */ import { get, post } from './http'Copy code
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 copy code
We can use API JS is encapsulated as follows:
export const apiAddress = p => post('api/v1/users/my_address/address_edit_before', p);Copy code
We define an apiaddress method. This method has a parameter p, which is the parameter object we carry when requesting the interface. Then we call the post method that we encapsulate. The first parameter of the post method is our interface address. The second parameter is the p parameter of apiAddress, that is, the parameter object carried by the request interface. Finally, export apiaddress through export.
Then we can call our api interface in our page as follows:
import { apiAddress } from '@/request/api';// Import our api interface export default { name: 'Address', created () { this.onLoad(); }, methods: { // get data onLoad() { // Call the api interface and provide two parameters apiAddress({ type: 0, sort: 1 }).then(res => { // Other operations after successful data acquisition .................. }) } } }Copy code
Other api interfaces are in Pai JS, just continue to expand below. Friendly tips, write notes for each interface!!!
One advantage of api interface management is that we unify and centralize the api. If we need to modify the interface in the later stage, we will directly in api JS, instead of going to every page to find our interface and then modifying it, it will be very troublesome. The key is that if the amount of modification is relatively large, the specification gg will be changed. In addition, if we modify the interface directly in our business code, we can easily move our business code accidentally, causing unnecessary trouble.
Well, finally, I'll present the completed axios packaging code.
/**axios encapsulation * Unified processing of request interception, corresponding interception and error */ import axios from 'axios';import QS from 'qs'; import { Toast } from 'vant'; import store from '../store/index' // Environment switching 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/'; } // Request timeout axios.defaults.timeout = 10000; // post request header axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; // request interceptor axios.interceptors.request.use( config => { // Before sending a request every time, judge whether there is a token. If there is a token, it will be added to the header of the http request instead of adding it manually every time // Even if there is a token locally, it may be expired, so the return status should be judged in the response interceptor const token = store.state.token; token && (config.headers.Authorization = token); return config; }, error => { return Promise.error(error); }) // Response interceptor axios.interceptors.response.use( response => { if (response.status === 200) { return Promise.resolve(response); } else { return Promise.reject(response); } }, // The server status code is not 200 error => { if (error.response.status) { switch (error.response.status) { // 401: not logged in // If you are not logged in, you will jump to the login page and carry the path of the current page // After successful login, return to the current page. This step needs to be operated on the login page. case 401: router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); break; // 403 token expired // Prompt users when login expires // Clear local token and empty token object in vuex // Jump to login page case 403: Toast({ message: 'Login expired, please login again', duration: 1000, forbidClick: true }); // Clear token localStorage.removeItem('token'); store.commit('loginSuccess', null); // Jump to the login page and pass the fullPath of the page to be viewed. After successful login, jump to the page to be visited setTimeout(() => { router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); }, 1000); break; // 404 request does not exist case 404: Toast({ message: 'The network request does not exist', duration: 1500, forbidClick: true }); break; // For other errors, directly throw the error prompt default: Toast({ message: error.response.data.message, duration: 1500, forbidClick: true }); } return Promise.reject(error.response); } } ); /** * get Method, corresponding to get request * @param {String} url [url address of the request] * @param {Object} params [Parameters carried during request] */ 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, corresponding to the post request * @param {String} url [url address of the request] * @param {Object} params [Parameters carried during request] */ 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 code
to update
The packaging of axios varies according to different requirements. Thank you very much for some pertinent suggestions in the comments. I also thought about it and improved it according to different needs. The main changes are as follows:
1. Optimize the axios encapsulation and remove the previous get and post
2. Treatment of network disconnection
3. More modular api management
4. There are multiple interface domain names
5. Mount API to Vue Omit the introduction steps on prototype
http. To optimize the axios package in JS, first paste the code directly:
/** * axios encapsulation * Unified processing of request interception, response interception and error */ import axios from 'axios'; import router from '../router'; import store from '../store/index'; import { Toast } from 'vant'; /** * Prompt function * It is forbidden to click the mask and close it after one second */ const tip = msg => { Toast({ message: msg, duration: 1000, forbidClick: true }); } /** * Jump to login page * Carry the route of the current page in order to return to the current page after completing the login on the login page */ const toLogin = () => { router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); } /** * Unified processing of errors after request failure * @param {Number} status Status code of request failure */ const errorHandle = (status, other) => { // Status code judgment switch (status) { // 401: not logged in, jump to the login page case 401: toLogin(); break; // 403 token expired // Clear the token and jump to the login page case 403: tip('Login expired, please login again'); localStorage.removeItem('token'); store.commit('loginSuccess', null); setTimeout(() => { toLogin(); }, 1000); break; // 404 request does not exist case 404: tip('The requested resource does not exist'); break; default: console.log(other); }} // Create an axios instance var instance = axios.create({ timeout: 1000 * 12}); // Set post request header instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; /** * request interceptor * Before each request, if there is a token, the token will be carried in the request header */ instance.interceptors.request.use( config => { // In the login process control, the login status of the user is judged according to whether there is a token locally // However, even if the token exists, it may be expired, so the token is carried in each request header // The background judges the user's login status according to the carried token and returns the corresponding status code to us // Then we can perform some unified operations according to the status code in the response interceptor. const token = store.state.token; token && (config.headers.Authorization = token); return config; }, error => Promise.error(error)) // Response interceptor instance.interceptors.response.use( // Request succeeded res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res), // request was aborted error => { const { response } = error; if (response) { // The request has been issued, but it is not in the range of 2xx errorHandle(response.status, response.data.message); return Promise.reject(response); } else { // Deal with network disconnection // eg: update the network status of state when the request times out or is disconnected // network status is in app One of the Vue hidden global control components is displayed // The refresh and re acquisition of data in the disconnection component will be described in the disconnection component store.commit('changeNetwork', false); } }); export default instance;Copy code
This axios is similar to the previous axios, with the following changes:
1. Remove the encapsulation of the previous get and post methods, and create an axios instance and export it with the export default method, which makes it more flexible to use.
2. The value of baseUrl controlled by environment variable is removed. Considering that the interface will have multiple different domain names, it is prepared to control the interface domain name through js variable. This point will be introduced in the api.
3. Request timeout is added, that is, the processing of network disconnection status. The next idea is to control the display and hiding of the disconnection prompt component by updating the network status in vuex when the network is disconnected. There will be a prompt for reloading the data when the network is disconnected, which will be introduced later.
4. Extract the common functions, simplify the code, and try to ensure the principle of single responsibility.
Let's talk about the api and consider the following requirements:
1. More modular
2. It is more convenient for multiple developers to effectively reduce and solve naming conflicts
3. There are multiple cases of processing interface domain names
Here, we have created an api folder with an index js and a base js, and multiple interface js files divided according to modules. index.js is an api exit, base js manages the interface domain name, while other js are used to manage the interfaces of various modules.
Put index first JS code:
/** * api Unified exit of interface */ // Article module interface import article from '@/api/article'; // Interfaces to other modules // Export interface export default { article, // ...... }Copy code
index.js is the exit of an api interface. In this way, the api interface can be divided into multiple modules according to the function, which is conducive to the collaborative development of multiple people. For example, one person is only responsible for the development of one module, and it can also facilitate the naming of the interface in each module.
base.js:
/** * Interface domain name management */ const base = { sq: 'https://xxxx111111.com/api/v1', bd: 'http://xxxxx22222.com/api' } export default base;Copy code
Through base JS to manage our interface domain name. No matter how many we have, 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 Module interface list */ import base from './base'; // Import interface domain name list import axios from '@/utils/http'; // Import the axios instance created in http import qs from 'qs'; // Whether to import qs module according to requirements const article = { // News list articleList () { return axios.get(`${base.sq}/topics`); }, // News details, demo articleDetail (id, params) { return axios.get(`${base.sq}/topic/${id}`, { params: params }); }, // post submission login (params) { return axios.post(`${base.sq}/accesstoken`, qs.stringify(params)); } // Other interfaces } export default article;Copy code
1. By directly introducing our encapsulated axios instance, and 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 of the data submitted during the post request.
2. The requested configuration is more flexible. You can make a different configuration for a certain demand. As for the priority of configuration, the axios document makes it clear that the order is: in lib / defaults JS: the default value of the library found, then the "defaults" attribute of the instance, and finally the "config" parameter of the request. 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 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 routing file import store from './store' // Import vuex file import api from './api' // Import api interface Vue.prototype.$api = api; // Mount the api on the prototype of vue and copy the code
Then we can call the interface in the page like this, eg:
methods: { onLoad(id) { this.$api.article.articleDetail(id, { api: 123 }).then(res=> { // Perform some actions }) } }Copy code
Let's talk about the handling of network disconnection. Here is only a simple example:
<template> <div id="app"> <div v-if="!network"> <h3>I have no Internet</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 to achieve another purpose of returning empty data onRefresh () { this.$router.replace('/refresh') } } } </script>Copy code
This is app vue, here is a brief demonstration of disconnection. At http JS describes that we will update the network status in vue when the network is disconnected. Here, we judge whether to load the disconnected component according to the network status. When the network is disconnected, the disconnected components are loaded, and the components of the corresponding page are not loaded. When we click refresh, we can retrieve the data by jumping to the refresh page and returning immediately. So we need to create a new 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 code
This is a global network disconnection prompt. Of course, it can also be operated according to your own project requirements. Different people have different opinions on the specific operation