Vue Axios封裝

前言

現在,在項目中,廣泛採用Axios庫進行Http接口請求。它是基於promise的http庫,可運行在瀏覽器端和node.js中。此外還有攔截請求和響應、轉換JSON數據、客戶端防護XSRF等優秀的特性。axios源碼javascript

「連接-axios中文文檔vue

考慮到各個項目實際使用時寫法混亂,不統一。對Axios進行一下通用化的封裝,目的是幫助簡化代碼和利於後期的更新維護,儘可能通用化。java

項目說明

項目代碼自己依附於@vue/CLI3構建,固然經過剝離vue代碼,該Axios封裝也能夠直接應用到其餘項目中。node

封裝要達成的目標:webpack

  • 統一維護管理接口;
  • 支持接口代理轉發;
  • 讀取環境配置,區分處理環境。
  • 攔截請求和響應,處理登陸超時、404等異常狀況;
  • 根據請求的配置匹配接口URL前綴且做支持作特殊處理。

封裝

安裝axios

npm install axios;
複製代碼

建立實例

經過建立實例,操做實例的方式進行接口請求。ios

//request.js
import axios from 'axios'; // 引入axios
import Qs from 'qs'; // 引入qs模塊,用來序列化post類型的數據
import { autoMatch, checkStatus } from './utils'; // 處理函數
import { Toast } from 'mint-ui'; //提示框

// 建立axios實例
const instance = axios.create({
  // baseURL: process.env.BASE_URL,
  timeout: 30000, // 請求超時時間
  // `transformRequest` 容許在向服務器發送前,修改請求數據
  transformRequest: [function (data) {
    // 對 data 進行任意轉換處理
    return data;
  }],
  // `transformResponse` 在傳遞給 then/catch 前,容許修改響應數據
  transformResponse: [function (data) {
    // 對 data 進行任意轉換處理
    return JSON.parse(data);
  }]
})
複製代碼

添加請求和響應攔截器

給實例添加請求和響應攔截器,根據實際數據格式進行相應的處理。
好比:
請求時,應後端要求根據Content-type設置data傳參格式;
響應時,統一處理登陸超時的狀況和請求失敗的錯誤提示處理等。git

//request.js
// 實例添加請求攔截器
instance.interceptors.request.use(function (config) {
  // 在發送請求以前作處理...
  config.headers = Object.assign(config.method === 'get' ? {
    'Accept': 'application/json',
    'Content-Type': 'application/json; charset=UTF-8'
  } : {
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
  }, config.headers);

  if (config.method === 'post') {
    const contentType = config.headers['Content-Type'];
    // 根據Content-Type轉換data格式
    if (contentType) {
      if (contentType.includes('multipart')) { // 類型 'multipart/form-data;'
        // config.data = data;
      } else if (contentType.includes('json')) { // 類型 'application/json;'
        // 服務器收到的raw body(原始數據) "{name:"nowThen",age:"18"}"(普通字符串)
        config.data = JSON.stringify(config.data);
      } else { // 類型 'application/x-www-form-urlencoded;'
        // 服務器收到的raw body(原始數據) name=nowThen&age=18
        config.data = Qs.stringify(config.data);
      }
    }
  }
  return Promise.resolve(config);
}, function (error) {
  // 對請求錯誤作處理...
  return Promise.reject(error);
});

// 實例添加響應攔截器
instance.interceptors.response.use(function (response) {
  // 對響應數據作處理,如下根據實際數據結構改動!!...

  // const { reason_code } = response.data || {};
  // if (reason_code === '001') { // 請求超時,跳轉登陸頁
  // const instance = Toast('請求超時,即將跳轉到登陸頁面...');
  // setTimeout(() => {
  // instance.close();
  // window.location.href = '/login';
  // }, 3000)
  // }
  return Promise.resolve(checkStatus(response));
}, function (error) {
  // 對響應錯誤作處理...
  return Promise.reject(checkStatus(error.response));
});
複製代碼

處理錯誤狀態:github

//utils.js
export function checkStatus (response) {
  const status = response.status || -1000; // -1000 本身定義,鏈接錯誤的status
  if ((status >= 200 && status < 300) || status === 304) {
    // 若是http狀態碼正常,則直接返回數據
    return response.data;
  } else {
    let errorInfo = '';
    switch (status) {
      case -1:
        errorInfo = '遠程服務響應失敗,請稍後重試';
        break;
      case 400:
        errorInfo = '400:錯誤請求';
        break;
      case 401:
        errorInfo = '401:訪問令牌無效或已過時';
        break;
      case 403:
        errorInfo = '403:拒絕訪問';
        break;
      case 404:
        errorInfo = '404:資源不存在';
        break;
      case 405:
        errorInfo = '405:請求方法未容許'
        break;
      case 408:
        errorInfo = '408:請求超時'
        break;
      case 500:
        errorInfo = '500:訪問服務失敗';
        break;
      case 501:
        errorInfo = '501:未實現';
        break;
      case 502:
        errorInfo = '502:無效網關';
        break;
      case 503:
        errorInfo = '503:服務不可用'
        break;
      default:
        errorInfo = `鏈接錯誤`
    }
    return {
      status,
      msg: errorInfo
    }
  }
}
複製代碼

封裝實例

實例封裝到async\await異步函數中。web

//request.js
const request = async function (opt) {
  try {
    const options = Object.assign({
      method: 'get',
      ifHandleError: true // 是否統一處理接口失敗(提示)
    }, opt);
    // 匹配接口前綴 開發環境則經過proxy配置轉發請求; 生產環境根據實際配置
    options.baseURL = autoMatch(options.prefix);

    const res = await instance(options);
    // console.log(res);
    if (!opt.ifHandleError) { // 自定義參數,是否容許全局提示錯誤信息
      Toast(res.error || '請求處理失敗!')
    }
    return res;
  } catch (err) {
    if (!opt.ifHandleError) { // 自定義參數,是否容許全局提示錯誤信息
      Toast(err.msg || '請求處理失敗!')
    }
    return err;
  }
}
複製代碼
  1. 此處能夠增長對請求入參進行合併或默認值處理;
  2. 自定義參數配置

項目中統一處理報錯提示。但有時對於接口請求比較多的狀況,不少時候並不但願某些接口被全局報錯提示。這時這種接口能夠設置自定義的ifHandleError參數處理,不進行全局錯誤提示,而是到業務代碼中再作處理。
同理,其餘的一些特殊業務狀況也能夠經過自定義參數處理。shell

  1. 根據自定義的prefix參數匹配接口前綴;

對於項目中請求多個後端的接口時,每一個接口在請求封裝時附帶prefix參數,根據prefix的值autoMatch函數統一處理,自動匹配接口前綴。

  • 開發環境可經過webpack proxy配置轉發請求。
  • 生產環境讀取實際配置(根據實際項目的狀況)。

autoMath方法:

// utils.js
const isDev = process.env.NODE_ENV === 'development'; // 開發 or 生產

// 匹配接口前綴
export function autoMatch (prefix) {
  let baseUrl = '';
  if (isDev) {
    // 開發環境 經過proxy配置轉發請求;
    baseUrl = `/${prefix || 'default'}`;
  } else {
    // 生產環境 根據實際配置 根據 prefix 匹配url;
    // 配置來源 根據實際應用場景更改配置。(1.從全局讀取;2.線上配置中心讀取)
    switch (prefix) {
      case 'baidu':	
        baseUrl = window.LOCAL_CONFIG.baidu;
        break;
      case 'alipay':
        baseUrl = window.LOCAL_CONFIG.alipay;
        break;
      default:
        baseUrl = window.LOCAL_CONFIG.default;
    }
  }
  return baseUrl;
}
複製代碼

開發環境接口代理

webpack配置,好比:@vue/CLI3在vue.config.js中配置:

devServer: {
  proxy: {
    '/baidu': {
      target: 'http://10.59.81.31:8088',
        changeOrigin: true,
          pathRewrite: { '^/baidu': '' }
    },
      '/default': {
        target: 'http://10.59.81.31:8088',
          changeOrigin: true,
            pathRewrite: {'^/default' : ''}
      },
  }
},
複製代碼

統一配置接口

在一個新文件apiUrl.js中統一管理項目所有的接口,方便維護。

//apiUrl.js
export const apiUrl = {
  login: '/api/login',
  loginOut: '/api/loginOut',
  qryPageConfig: '/test/qryPageConfig',
  setPageConfig: '/test/setPageConfig',
  //...
}
複製代碼

生成接口

index.js中:

import Vue from 'vue';
import request from './request';
import { apiUrl } from './apiUrl';

let services = {};

Object.entries(apiUrl).forEach((item) => {
  services[item[0]] = function (options = {}) {
    return request(Object.assign({
      url: item[1]
    }, options))
  }
})

// 將services掛載到vue的原型上
// 業務中引用的方法:this.$services.接口名(小駝峯)
Object.defineProperty(Vue.prototype, '$services', {
  value: services
});

export default services;

複製代碼

在上述代碼中,經過讀取配置的接口信息(名稱和請求路徑),生成所有接口。 並掛載到vue的原型上,這樣在業務中就能夠經過this.$services.接口名直接調用對應的接口。

固然,這是本項目使用的方法。 因具體項目而異,也能夠不掛載到Vue原型上,使用services模塊;或直接調用request.js封裝的請求函數。

業務調用

如下爲使用事例:

// index.vue methods中
methods: {
  // 獲取頁面配置信息接口 get Promise
  $_qryPageConfig() {
    this.$services.qryPageConfig({
      method: 'get', //默認
      params: {
        page: 'login'
      },
    }).then((res) => {
      this.pageConfig = res.data;
    }).finally(() => {
      ...
      this.close();
    });
  },
	// 設置頁面配置接口 post async/await
  $_order: async function () {
    this.loading = true;
    const res = await this.$services.setPageConfig({
      prefix: 'baidu', //匹配url前綴
      ifHandleError: true, //不對該接口進行全局錯誤提示。
      method: 'post',
      headers: {
        'Content-Type': 'application/json; charset=UTF-8',
      },
      data: {
        userId: this.idCard,
        userName: this.realName,
        color: 'red',
      }
    })
    this.loading = false;
    if (res.data) {
      Total('success');
    }
  },
}
複製代碼

項目代碼

項目代碼自己依附於@vue/CLI3構建,是CLI3構建移動H5端應用的一部分。固然經過剝離vue代碼,該Axios封裝也能夠直接應用到其餘項目中。

項目源碼:
@vue/CLI3構建移動H5端應用:juejin.im/post/5d674d…

Github連接:github.com/now1then/vu…

相關文章
相關標籤/搜索