• 封装一个好用的axios,省时又省力,真香!!


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

    一、初识 axios

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

    它具有以下几个优点

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

    优点还挺多,上手试试。

    二、封装 axios

    1. 安装依赖

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

    yarn add axios
    
    • 1

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

    import axios from 'axios'
    
    • 1

    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请求头的设置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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('网络出现故障,请稍后再试')
            }
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    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
            }
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    三、使用 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接口
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 在页面使用

    import { userLogin } from '@/api/user'
    
    userLogin({
      username: this.username,
      password: this.password, // 记得加密QAQ
    }).then(res => {
      this.$msg.success('登录成功')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    四、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接口
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3. 引入及使用

    main.js引入所有api

    import Vue from 'vue'
    import api from '@/api'
    
    Vue.prototype.$api = api
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用

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

    五、总结

    完结撒花😁😁😁

    本文首发于足各路的博客,后续会同步更新到掘金知乎CSDN关注足各路、前端不迷路!

  • 相关阅读:
    研发效能工程实践-代码评审
    面试:dex文件结构
    Linux之高级文件系统管理
    PHP+学生成绩管理系统 毕业设计-附源码201829
    垃圾分类资讯易语言代码
    Spire.Office for Java 8.10.2 同步更新Crk
    Appium问题及解决:打开Appium可视化界面,点击搜索按钮,提示inspectormoved
    OpenCV练习(1)签名修复
    C++ Tutorials: C++ Language: Compound data types: Arrays
    如何使用轻量应用服务器搭建Typecho个人博客系统?
  • 原文地址:https://blog.csdn.net/weixin_44388523/article/details/125549666