简单聊一聊我在项目中是如何封装axios以及如何统一管理api接口,其主要目的是简化代码,使代码结构更清晰,便于后期更新维护。

一、初识 axios

axios - 一个易用、简洁且高效的http库

它具有以下几个优点

  • 支持node端和浏览器端 - 同样的API,node和浏览器全支持,平台切换无压力
  • 支持 Promise - 使用Promise管理异步,告别传统callback方式
  • 丰富的配置项 - 支持拦截器等高级配置
  • 社区支持 - axios相关的npm包数量一直在增长

优点还挺多,上手试试。

二、封装 axios

1. 安装依赖

可以使用npm、yarn等进行安装,这里我使用 yarn

yarn add axios

引入axios。我一般是在src下创建一个utils文件夹,在其中新建一个request.js放置封装好的axios

import axios from 'axios'

2. 创建实例

// 创建实例
const instance = axios.create()

// 创建实例后修改默认值
axios.defaults.baseURL = process.env.NODE_ENV == 'development' ? 'http://127.0.0.1:8081' : 'https://api.example.com' // 默认请求地址,需根据环境判断请求的路径
axios.defaults.timeout = 10000 // 超时时间,单位毫秒
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded' // post请求头的设置

3. 请求拦截

/**
 * 请求拦截器
 * 每次请求前,如果存在token则在请求头中携带token
 */
axios.interceptors.request.use(
    config => {
        LoadingBar.start()
        // 添加token
        const token = getToken()
        token && (config.headers.Authorization = "Bearer " + token)
        return config
    },
    error => Promise.error(error)
)

4. 响应拦截

/**
 * 响应拦截器
 * 每次请求后,判断请求状态码是否正确,及数据做处理
 */
axios.interceptors.response.use(
    /**
     * 传输层:接口正常或异常,用http状态码
     * 业务层:业务正常或异常,用自定义状态码
     */
    // 请求成功
    res => {
        LoadingBar.stop()
        // HTTP 状态码
        if (res.status !== 200) {
            return Promise.reject(res)
        }

        // 业务状态码
        let code = res.data.code
        if (!code || code === 2000) {
            // 无code,则请求的是html页面;有code,则返回请求的数据
            return Promise.resolve(res.data)
        }

        errorHandle(code, res.data.msg)
        return Promise.reject(false)
    },
    // 请求失败
    error => {
        LoadingBar.stop()
        const { response } = error
        if (response) {
            // 请求已发出,但是不在2xx的范围 
            errorHandle(response.status, response.data.message)
            return Promise.reject(response)
        } else {
            tip('网络出现故障,请稍后再试')
        }
    }
)

5. 错误处理

/**
 * 请求失败后的错误统一处理
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status, msg) => {
    // 状态码判断
    switch (status) {
        // 2002: 用户名/密码错误
        case 2002:
            tip('用户名或密码错误!')
            break
        // 4003: token过期,清除token并跳转登录页
        case 4003:
            toLogin("登录信息过期")
            break
        // 其他状态码
        ...
        default:
            tip('后台维护中,请稍后再试')
    }
}

/**
* 提示函数
*/
const tip = msg => {
    // 使用UI框架自带的错误弹框即可
    Vue.prototype.$msg.error(msg)
}

/**
 * 跳转登录页
 * 携带当前页面路由,以便在登录完成登录后返回当前页面
 */
const toLogin = async (msg) => {
    // 移除token、用户信息

    // 跳转登录页
    router.replace({
        path: '/login',
        query: {
            redirect: router.currentRoute.fullPath
        }
    })
}

三、使用 axios

1. 创建api接口

user模块为例,在src目录下新建api文件夹,用来存放项目的所有接口请求,新建user.js,代码如下:

import axios from '@/utils/request'

/**
 * @description: 用户登录
 * @param {String} username 用户名
 * @param {String} password 密码(aes加密)
 */
export const userLogin = params => {
    return axios.post('/user/login', params)
}
// 其他user接口
...

2. 在页面使用

import { userLogin } from '@/api/user'

userLogin({
  username: this.username,
  password: this.password, // 记得加密QAQ
}).then(res => {
  this.$msg.success('登录成功')
})

四、api 进阶

很多项目都是通过以上方式来调用接口的,但是这种方式有个弊端,在每个页面都得引入需要的API,如果某个界面用到不同模块的接口,得引入好几次,造成代码冗余。

在B站自学的时候,发现了一个更合适的方法,下面就来详细讲解一下。

1. 创建api接口

user模块作为一个整体导出。

import axios from '@/utils/request'

export const user = {
    /**
     * @description: 用户登录
     * @param {String} username 用户名
     * @param {String} password 密码(aes加密)
     */
    userLogin(params) {
        return axios.post('/user/login', params)
    }
    // 其他user接口
    ...
}

2. 创建index.js

在使用api时,经常需要importexport各种模块,那么有没有什么办法可以简化这种操作呢?答案是肯定的,下面就为大家介绍一下require.context的基本使用方法:

require.context(directory,useSubdirectories,regExp),三个参数分别如下:

  • directory 要查找的文件路径
  • useSubdirectories 是否查找子目录
  • regExp 要匹配文件的正则

具体使用方式见:webpack中require.context的作用
require.context官网地址:webpack-requirecontext

本案例使用如下,将所有index.js同级api导入,导入后统一导出,最后在main.js将所有api挂载到vue

// 批量导出文件
const requireApi = require.context(
    // api 目录的相对路径
    '.',
    // 是否查询子目录
    false,
    // 查询文件的一个后缀
    /.js$/
)

let module = {}
requireApi.keys().forEach((key,index) => {
	if(key === './index.js') return
	Object.assign(module, requireApi(key))	
})

export default module

3. 引入及使用

main.js引入所有api

import Vue from 'vue'
import api from '@/api'

Vue.prototype.$api = api
...

使用

this.$api.user.userLogin().then(res => {
  // 接口响应成功后的一些处理...
})