Interface encapsulation necessity

In large front-end projects, when there are many interfaces to implement data input, outflow and additional interception, combined with state management, resist XSRF attacks, etc., unified management API interface becomes the link that large front-end projects must face. Axios, the most popular PROMISe-based HTTP library that can run on both the browser and server side, has become the preferred option for most front-end projects.

POST serialization

We can also serialize with json.stringify, but the support for complex Objectjson.stringify is not as good as qs.stringify. So by introducing the QS library, QS can help us serialize deeply nested JSON and Array forms, making our API package compatible with more scenarios.

var a = {name:'hehe',age:10};
 qs.stringify(a)
// 'name=hehe&age=10'
JSON.stringify(a)
// '{"name":"hehe","age":10}'
Copy the code

Exception: Nowadays, most background projects can fetch JSON, array, etc., in the body. In some cases, the background may read the string information directly. In this case, the JSON and array format in qs.stringify encapsulation parameter cannot be retrieved, so we need to use json. stringify to handle

Axios handling of configuration

Before we do the axiOS secondary wrapping, let’s take a quick look at how AXIos handles configuration items. As you can see from the methods exposed by Axios, config can be configured on axios.defaults, as well as on interceptors and new instance config. The axiOS config is merged using the merge method:

axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
Copy the code

In addition to axios default and the new instance config, a default config is provided for initialization.

var DEFAULT_CONTENT_TYPE = {
  'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
  if(! utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
    headers['Content-Type'] = value; }}Copy the code

The axios config has this logic:

1. Merge defaultconfig with defaultconfig

2. Merge the result obtained in step 1 with the config on the new instance

Through analysis, we can directly configure the requested interface, which is more convenient to adapt to various scenarios in one step

default config

Axios gives me a default set of attributes that I can assign directly to axios.default. The assignment to axios. The official documentation provided me with some references, such as setting the default baseURL, putting tokens into header Authorization for tok-based requests, and setting the request type for POST.

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
Copy the code

Request interception and response interception

As an excellent HTTP request library, AXIos provides powerful request and response interception capabilities.

Request to intercept
// Vuex import store from'@/store'. Axios. Interceptors. Request. Use (config = > {/ / add token to the request of the header inside const token = store. State. The token. config.headers.common['Authorization'] = token
  // loading
  return config
}, error => {
  console.log(error)
  return Promise.reject(error)
})
Copy the code

It is possible to do the pre-processing of a request through an interceptor, for example, by adding a token to a header, which is more common. Of course, it is also possible to handle tokens in default. So most interceptor operations on the web can be done in defaults, and there’s no difference; I think pre-request interception can be combined with the use of some timers and front-end monitoring related plug-ins.

The response to intercept

Need to pay attention to the response to intercept the execution of the order, to perform axios. Interceptors. Response. Use and then execute the normal response processing;

/ / response interceptor axios. Interceptors. Response. Use (response = > {/ / here's the response returned by the HTTP status code in the case of 2 xx // If the HTTP status code is not 2XX, it can be used to handle different HTTP status error => {if (error.response.status) {            
            switch (error.response.status) {                
                case401: // Unlogged processingcase403: // Processing of insufficient permissionsbreak; 
                case404: // 404 request does not existbreak; // Other errors, directly throw error message default: // default processing}returnPromise.reject(error.response); }}});Copy the code

Axios is fully configured

According to the specific config analysis of AXIos above, we can realize the configuration of multiple scenarios by adding merge combined with encapsulation method. You can implement such things as whether to carry cookies across domains, whether to carry loading, and configuring requests for special interfaces to wait

You can create a config.js

const configMap = {
  defaultConfig: {
    withCredentials: false,
    baseURL: path.baseUrl,
    headers: {
      post: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
    },
  },
  long: {
    timeout: 60000,
  },
  nocookie: {
    withCredentials: false,},...Copy the code

By introducing it in HTTPJS

import configMap from './config'
import { showFullScreenLoading, tryHideFullScreenLoading } from './loading'
import merge from 'lodash.merge'. merge(axios.defaults, configMap['defaultConfig'])
function handleTypeString(type) {
  type.toLowerCase().split(The '-').map(item => merge(axios.defaults, configMap[item]))
}
export default {
  post(url, data, type) {
    handleTypeString(type)
    return axios({
      method: 'post'.Copy the code

This allows for a configuration combination of multiple interface requests, which override the previous ones, one after the other, to fully configure the processing of AXIOS requests;

Used in VUE

import http from "@/api/http";
import path from "@/api/path"; // Async await is more eleganttest() {
  const res = await http.post(path.test, params, "long-nocookie");

Copy the code

Some people prefer to use partial functions to wrap another layer, or you can add another layer to wrap the call directly.

test(param)
Copy the code

Handling the loading state globally

Encapsulate load.js to handle the case that some URL request interfaces need to load chrysanthemum graph; NeedLoadingCount needs to be set to record the processing of multiple loading request interfaces.

import { Loading } from 'element-ui';
let loading; 
function startLoading() {// Use the Element loading.tart method loading = loading. service({lock:true,
    text: 'loading... ',
    background: 'rgba (0, 0, 0, 0.5)'}); }function endLoading() {// Use the Element loading.close method loading.close(); } // Use needLoadingCount to log in multiple places using loadinglet needLoadingCount = 0;
export function showLoading() {
  if(needLoadingCount === 0) { startLoading(); } needLoadingCount++; //eslint-disable-line }export function tryHideLoading() {
  if(needLoadingCount >= 0) needLoadingCount--; //eslint-disable-lineif(needLoadingCount === 0) { endLoading(); }}Copy the code

Configure in HTTP

    if (type= = ='long') {
      showFullScreenLoading()
      returnaxios(config).then(response=>{ tryHideFullScreenLoading() rerurn response; })} // Other AXIos requests without loadingreturn axios(config)
Copy the code

Multiple environment switching packages

In the SPA scenario where the front and back ends are separated, various environment parameters such as AXIos baseUrl are preset; Then packed into a static file, upload nginx or tomcat HTTP server, similar from local development to test, offer different background to use static files, may be different background Settings interface address is different, in order to avoid to pack one by one, we need to configure a packaging for different domain environment;


import merge from 'lodash.merge'
const path = {
  baseUrl: 'http://localhost:3000',
  login: '/users/login'.test: '/test'}; const pathMap = {'http://localhost:3001': { baseUrl: 'http://localhost:3001' },
    'http://localhost:3002': { baseUrl: 'http://localhost:3002',login:'/login' },
}

const getClientIdByLocation = () => {
  const { href } = window.location;
  const matchedKey = Object.keys(pathMap).filter(url => href.indexOf(url) > -1);
  merge(path, pathMap[matchedKey])
};
getClientIdByLocation();
export default path;
Copy the code

You can directly configure the path object corresponding to different urls in pathMap to handle the baseUrl corresponding to different urls and different sub-routes