经验|axios封装与api管理:简洁实用

11,906 阅读5分钟

经验|axios封装与api管理:简洁实用

一、先看封装前后的效果

1.封装前后效果对比

封装后的axios结合es6使用,如下图,看起来简洁干净,用起来方便实用

image.png

封装前可能是这样的,如下图

image.png

二、选择这种封装方式的原因

封装前后两种方式,在具体接口调用上都是差不多的,主要是在声明和组织接口上有“肉眼可见”的差距,封装后的axios再结合es6的箭头函数,简洁干净实用了,要是花里胡哨,中看不中用又麻烦,那不值得记录。多年经验,见过许多种axios封装和接口组织方式,这种最受我欢迎的,原因如下:

1.简洁干净

2.实用不麻烦

3.好管理便于维护

三、封装过程与结果

1.封装过程

1.安装axios插件,创建 api 文件夹,并在文件夹下床架 axios.js 文件,并在文件中,创建引入 axios,然后创建axios实例

// npm 安装 axios 命令:npm install axios -s
// yarn 安装 axios 命令:yarn add axios -s

import axios from 'axios'

// 创建axios实例
const http = axios.create({
  // 请求头配置 token
  headers: {
    'token': localStorage.getItem('token') || ''
  },
  // 基础路径
  baseURL: basePath,
  // 请求连接超时设置
  timeout: 2 * 60 * 1000,
  // 表示跨域请求时是否需要使用凭证,开启后,后端服务器要设置允许开启
  withCredentials: true,
})

2.完善 axios 请求与响应拦截器

// request 请求拦截器
http.interceptors.request.use(
  config => {
    // 发起请求时,重新获取最新的token,这一步有时很重要,因为创建 axios 实例的时候,
    // 获取到的 token 未必是有效的,或说未必能获取到
    const token = localStorage.getItem('token')

    // 特殊配置:设置下载获取 excel 接口的返回值为 blob。这在异步下载文件时有时会显得很有用。
    if (config.url === '/api/excel/download') {
      config.responseType = 'blob'
    }

    // 特殊配置:登录接口,将 请求的头的 token 设置为空字符串
    if (config.url === '/author/login') {
      config.headers['token'] = ''
    } else {
      config.headers['token'] = token || ''
    }

    return config
  },
  error => {
    console.warn(error)
    return Promise.reject(error)
  }
)

// response 响应拦截器
http.interceptors.response.use(
  response => {
    const res = response.data

    // 特殊配置:code为 -10086,表示资源不存在,跳转到提示页(404)
    if (res.code === -10086) {
      router.push({
        name: 'ErrorPage',
        query: { type: '404' }
      }).then()
      return res
    }

    // 特殊配置:-12306,无权限,跳转到提示页
    if (res.code === -12306) {
      router.push({
        name: 'ErrorPage',
        query: { type: 'authority' }
      }).then()
      return res
    }

    // code === -12345,token失效,跳转到登录页重新登陆
    if (res.code === -12345) {
      /* // 一个特殊功能:访问一个地址没权限,记录下该地址,登录获取后可以重定向到该地址
      let currentUrl = window.location.href
      const index = currentUrl.indexOf('#')
      const str = currentUrl.substr(index + 1, 6)
      // 如当前页不是登录页则跳转到登录页并缓存当前页地址
      if (str !== '/Login') {
        localStorage.setItem('currentUrl', window.location.href)
        router.push({ name: 'Login' }).then()
      } */

      router.push({ name: 'Login' }).then()
      return res
    }

    // 特殊配置:异步下载文件中有用,一般用不上,导出 word、excel 文件接口
    // 注意此时返回的是完整的 response,开放更多信息便于处理下载过程
    const url = response.config.url
    if (url === '/api/word/download' || url === '/api/excel/download') {
      response.headers.responseType = 'blob'
      return response
    }

    // 前后端约定 code 为100表示接口正常响应,取相反值则表示此时接口非正常响应,
    // 此时,统一开启接口返回的业务类型的错误提示,这样就不用在每个接口都处理,
    // 例如账号密错误时,自动捕获接口返回的业务类型的“错误”提示。注意这是业务类型的“错误”,不是bug。
    if (res.code !== 100) {
      Message({
        message: res.message || '服务器开小差啦,请稍后重试',
        type: 'error',
        duration: 3 * 1000
      })

      // 直接reject有时非常有用,统一拦截,这时,调用接口的回调.then()不会在执行,
      // 而是改为执行.catch(),这样做的好处是我们无需考虑在具体的每一个接口中
      // 的.then()是否是正常响应,只接受正常响应的 code 为 100 的。非100不进入.then()
      // 当然这对后端按约定编写接口有较高的要求,也有弊端,就是非100无法在.then()中接收到。
      // return Promise.reject(res.message)

      return res
    } else {
      return res
    }
  },
  error => {
    /* // 特殊处理:拦截更改提示信息
    if (error.message.indexOf('timeout') > -1) {
      error.message = '请求超时'
    }
    if (error.message.indexOf('Network') > -1) {
      error.message = '网络错误'
    } */

    /* // 特殊处理:禁止短时间内多次弹出提示,遗憾的是效果不理想,后续测验优化
    // 1000毫秒内最多只展示一次报错信息
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      Message({
        message: error.message,
        type: 'error',
        duration: 5 * 1000
      })
    }, 1000) */

    Message({
      message: error.message,
      type: 'error',
      duration: 3 * 1000
    })

    return Promise.reject(error)
  }
)

3.axios结合Promise封装出5常规请求,即是get,post,put,delete,以及用于上传文件的postMultipart

image.png

4.根据业务类型分模块创建 api 文件,组织管理接口,调用封装的5种请求方式

// api文件夹中的 user.js 
// 用户模块相关api
import {
  http,
  basePath,
  post,
  get,
  put,
  deleteJson,
  postMultipart,
} from './axios'

let userApi = {
  login: (p) => post('/auth/login', p),
}
export default userApi

5.汇总挂载到 vue 实例上,减少引入,方便调用

在 api 文件夹下,创建 index.js 文件,用于汇总 各模块接口,挂载到 vue 实例上,方便调用

// index.js 文件

import userApi from "./user";
import fileApi from "./file";
import orderApi from "./order";

const api = {
  ...userApi, // 用户相关接口
  ...fileApi, // 文件相关接口
  ...orderApi, // 订单相关接口
}

export default api

在入口文件 main.js 中引入 api 文件夹下的 index.js 并挂载到 vue 实例上。

// main.js 文件

import api from './api/index'
Vue.prototype.$api = api

6.具体页面功能中调用接口

// login.vue 调用
login () {
    const params = {
        account: this.account,
        // 密码加密传输,加密技术自选,这里以 jsencrypt 为例
        password: encrypt(this.password),
    }
    // 开启登录按钮的 loading
    this.loading = true
    this.$api.login(params).then(res => {
        // 处理相应的业务
        console.log(res)
    }).catch(error => {
        // catch 这一步已经在拦截器中统一处理了,所以实际中不用写catch,特殊情况例外。
        console.log(error)
    }).finally(() => {
        // 关闭loading
        this.loading = false
    })
},

2.封装结果

1.完整的 axios.js 文件

image.png

2.完整的 user.js 文件

image.png

3.完整的 login.vue 文件

image.png