前端封装 axios - 前端如何让接口调用更优雅

个人网站:aijianli.site/可以免费在线制作简历,提供PDF下载,方便快捷。

前端接口访问最常用的就是 axios ,基本上都会对 axios 进行封装后再使用,但是经历过的项目对 axios 的封装都是各不相同。以下介绍项目中遇到的 axios 使用的封装方式和自己探索出来的 axios 封装进行简单介绍。

一、项目中经历的 axios 封装方式

1.1 通用封装

首先,需要对 axios 进行请求拦截器和响应拦截器进行定义,这基本上是共识,每个项目中都会对 axios 加上请求拦截器和响应拦截器。如下:

js 复制代码
import axios from "axios";
import {
    Notification
} from "element-ui";
import qs from "qs";

// const baseUrl = "http://aijianli.site/api";
const baseUrl = "http://localhost:8081/api";
let config = {
    baseURL: baseUrl,
    timeout: 60000,
    responseType: 'json',
    headers: {
        ["Access-Control-Allow-Origin"]: '*',
        ['Pragma: no-cache']: false,
        ['Access-Control-Allow-Methods']: 'POST,GET,OPTIONS,DELETE',
        ['Access-Control-Allow-Headers']: 'x-requested-with,content-type'
    }
};
const instance = axios.create(config)

//请求拦截器
instance.interceptors.request.use(
    (config) => {
        if (config.url !== "/api/user/login" && config.url !== "/api/user/regist") { // 判断请求是否是登录接口
            config.headers.token = localStorage.getItem("token"); // 如果不是登录接口,就给请求头里面设置token
        }
        return config; // 返回这个配置对象,如果没有返回,这个请求就不会发送出去
    },
    (error) => {
        return Promise.reject(error);
    }
)

// 响应拦截器
instance.interceptors.response.use(
    (res) => {
        let code = res.data.state // 获取后端返回的状态码

        if (code === 200) { // 成功
            if (res.data.message) {
                Notification({
                    message: res.data.message,
                    type: "success"
                })
            }
            return res.data // 返回里面的数据,在使用这个axios时,获取到的东西就是这里返回的东西
        } else {
            Notification({
                message: res.data.message,
                type: "error"
            })
            return Promise.reject(res.data.message);
        }

    },
    (error) => {
        Notification({
            message: "服务器错误:" + error.message,
            type: "error"
        })
        return Promise.reject(error);
    }
)

export default instance;

1.2 常规使用方式 - 直接将 axios 实例挂载到全局

常规的使用,在完成 1.1 步骤后就直接将 axios 实例挂载到一个全局可访问的地方就行。例如,在 Vue 项目中,直接将 axios 实例挂载到 Vue 原型上即可。如下:

js 复制代码
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';

Vue.use(ElementUI)
// 注册模块
import "./components/util-js/global-regist-module.js";
import axiosInstance from "./assets/js/axiosUtil.js";

Vue.config.productionTip = false
import "../node_modules/element-ui/lib/theme-chalk/index.css";
import "./assets/js/core.js";
import "./assets/css/cover-elementui-style.less";
import router from './router';
Vue.prototype.$axios = axiosInstance;


new Vue({
  render: h => h(App),
  router: router
}).$mount('#resume-creator-user')

1.3 组件内调用

在组件中直接通过 this.$axios 即可使用 axios 发送请求

js 复制代码
 // 调用后台接口
this.$axios.get("/xxx").then(res=> {
   console.log(res)
})
// 调用后台接口
this.$axios.post("/xxx", data).then(res=> {
    console.log(res)
})

1.4 存在问题

以上仅仅是对 axios 进行了请求拦截器和响应拦截器进行了封装。在使用时需要显示调用 get 还是 post 放在,同时还要添加接口访问地址 url ,这种使用方式在不同的地方调用同一个接口都得复制一下 url ,有一定的不便性,同时,如果后期有 url 调整时,需要修改的地方较多。因此在自己的项目中,做了优化。见 二、自己优化的 axios 封装方式

二、自己优化的 axios 封装方式

2.1 axios 封装后端接口

针对上述的使用方式存在的问题,个人的习惯是将所有后端接口封装在一起,前端调用接口时就像是使用本地方法一样调用接口,只需要关心方法参数即可,不需要关心后端地址、请求是 post 还是 get。具体实现如下:

js 复制代码
import axios from "axios";
import {
    Notification
} from "element-ui";
import qs from "qs";

// const baseUrl = "http://aijianli.site/api";
const baseUrl = "http://localhost:8081/api";
let config = {
    baseURL: baseUrl,
    timeout: 60000,
    responseType: 'json',
    headers: {
        ["Access-Control-Allow-Origin"]: '*',
        ['Pragma: no-cache']: false,
        ['Access-Control-Allow-Methods']: 'POST,GET,OPTIONS,DELETE',
        ['Access-Control-Allow-Headers']: 'x-requested-with,content-type'
    }
};
const instance = axios.create(config)

//请求拦截器
instance.interceptors.request.use(
    (config) => {
        if (config.url !== "/api/user/login" && config.url !== "/api/user/regist") { // 判断请求是否是登录接口
            config.headers.token = localStorage.getItem("token"); // 如果不是登录接口,就给请求头里面设置token
        }
        return config; // 返回这个配置对象,如果没有返回,这个请求就不会发送出去
    },
    (error) => {
        return Promise.reject(error);
    }
)

// 响应拦截器
instance.interceptors.response.use(
    (res) => {
        let code = res.data.state // 获取后端返回的状态码

        if (code === 200) { // 成功
            if (res.data.message) {
                Notification({
                    message: res.data.message,
                    type: "success"
                })
            }
            return res.data // 返回里面的数据,在使用这个axios时,获取到的东西就是这里返回的东西
        } else {
            Notification({
                message: res.data.message,
                type: "error"
            })
            return Promise.reject(res.data.message);
        }

    },
    (error) => {
        Notification({
            message: "服务器错误:" + error.message,
            type: "error"
        })
        return Promise.reject(error);
    }
)

class axiosUtil {
    constructor() {
        this.axios = instance;
    }

    post(url, datas) {
        return this.axios.post(url, datas)
    }

    get(url, datas) {
        url = url + qs.stringify(datas, {
            addQueryPrefix: true
        })
        return this.axios.get(url);
    }

    //在此封装所有的后端接口
    /*-------------用户相关接口的开始-------------------------- */
    /**
     * 用户注册
     * @param {*} data 
     * @returns 
     */
    regist(datas) {
        return this.post("/user/regist", datas);
    }

    login(datas) {
        return this.post("/user/login", datas);
    }

    queryUserByPage(datas) {
        return this.post("/user/query/all", datas);
    }

    checkDeveloper(datas) {
        return this.get("/user/check/developer", datas);
    }

    blacklistTiggle(datas) {
        return this.get("/user/blacklist", datas);
    }

    querUserById() {
        return this.get("/user/queryById", {});
    }

    updateUserById(datas) {
        return this.post("/user/updateById", datas);
    }

    changePassword(datas) {
        return this.post("/user/change/password", datas);
    }

    developerApply() {
        return this.post("/user/developer/apply", {});
    }

    /*-------------用户相关接口的结束-------------------------- */

    /*-------------字典相关的接口的开始----------------------------- */
    /**
     * 查询地区字典
     * @returns 返回地区的第一层
     */
    queryRegion() {
        //地区字典的id
        let dId = "xxx";
        return this.queryByDid(dId);
    }

    /**
     * 查询出民族字典
     */
    queryNationality() {
        //民族字典的id
        let dId = "xxx";
        return this.queryByDid(dId);
    }

    /**
     * 查出性别字典
     * @returns 
     */
    queryGender() {
        let dId = "xxx";
        return this.queryByDid(dId);
    }

    queryByDid(dId) {
        return this.get("/dict/selectFirstLevel", {
            dId
        });
    }

    queryItemByParent(datas) {
        return this.get("/dict/selectItemsByParent", datas);
    }

    /*-------------字典相关的接口的结束----------------------------- */

    /*-------------简历信息相关的接口的开始--------------------------- */
    /**
     * 新增简历信息
     * @param {*} datas 
     * @returns 
     */
    addResumeInfo(datas) {
        return this.post("/resume/info/add", datas);
    }

    editResumeInfo(datas) {
        return this.post("/resume/info/edit", datas);
    }

    deleteResumeInfo(datas) {
        return this.get("/resume/info/delete", datas);
    }

    /**
     * 根据用户id查用户简历信息列表
     * @returns 
     */
    queryByUser(datas) {
        return this.get("/resume/info/queryByUser", datas);
    }

    /**
     * 根据简历信息id查出简历的所有信息
     * 结构为:
     * {
     * baseInfo: {}, //基础信息
     * }
     */
    queryAllInfoById(datas) {
        console.log(datas)
        return this.get("/resume/info/queryById", datas)
    }

    /*-------------简历信息相关的接口的结束--------------------------- */

    /*-------------简历模板接口开始------------------------ */
    /**
     * 通过用户信息查用户的模板,此功能只有开发者才有
     * 没有参数,用户信息后端直接从token中取
     */
    queryTemplateByUser() {
        return this.get("/template/queryByUser", {});
    }

    // ...

    /*-------------简历模板接口结束------------------------ */

    /*--------------简历接口开始 --------------------------*/
    addResume(datas) {
        return this.post("/resume/add", datas)
    }

    // ....

    /*--------------简历接口结束 --------------------------*/

}

export default new axiosUtil();

如上代码,将 axios 的实例对象封装在 axiosUtil 中

js 复制代码
class axiosUtil {
    constructor() {
        this.axios = instance;
    }
    // ...
}

对 axios 的 get,post 方法再封装

js 复制代码
class axiosUtil {
    constructor() {
        this.axios = instance;
    }

    post(url, datas) {
        return this.axios.post(url, datas)
    }

    get(url, datas) {
        url = url + qs.stringify(datas, {
            addQueryPrefix: true
        })
        return this.axios.get(url);
    }
}

业务接口直接调用封装后的 get, post, 外部使用只需要关心参数即可。将后端所有接口封装在此,当然,如果接口较多,也可以采用分模块封装的方式。

js 复制代码
 //在此封装所有的后端接口
    /*-------------用户相关接口的开始-------------------------- */
    /**
     * 用户注册
     * @param {*} data 
     * @returns 
     */
    regist(datas) {
        return this.post("/user/regist", datas);
    }

    login(datas) {
        return this.post("/user/login", datas);
    }

    queryUserByPage(datas) {
        return this.post("/user/query/all", datas);
    }

2.2 接口调用

完成以上封装,并将 axiosUtil 工具类挂载到 Vue 上,或者也可以放全局对象。在我自己的项目中是将 axiosUtil 工具类对象放在全局的对象 core.js 中,在调用时就像是调用一个本地方法一样。如下:

core.js

js 复制代码
import axios from "./axiosUtil.js";
class Core {
    constructor() {
        // 封装请求的 axios 在此
        this.$axios = axios;
     }
     // ...
}

window.core = new Core();

组件中使用: 以下两个例子,接口调用就像是调用本地的一个瓶Promise方法,不需要关心 url 和 请求方式(get,post,delete...)

js 复制代码
core.$axios.regist(this.formData)

this.$axios.queryAvailableTemplate().then(res => {
    let templates = res.data;
    templates.forEach(template => {
    // 如果当前模板没有实例存在,才加载当前模板的实例
    if (!this.$instances[template.t_url]) {
        // 加载模板 js
        this.loadProdTemplate(template.t_url);
    }
  })
})

2.3 存在问题

以上封装方式,虽然能够解决前端不需要重复写 url ,并且明确指定方法调用的是 get 还是 post 的问题,但是对于多人合作的项目来说,每次后端新增接口之后,就需要在 axiosUtil 中新增接口,维护起来不是特别方便。

三、解决想法

由后端进行封装,将 二 中封装的代码作为模板,由后端写脚本在每次新增接口或者接口变更时,将所有接口按模板的方式写入 js 文件,并将文件接口 js 文件直接部署到服务器上,前端使用时通过 script 标签引入。如此便能实现接口自动维护。

相关推荐
学习ing小白34 分钟前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进1 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er1 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063711 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl1 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码1 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347542 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
喝旺仔la2 小时前
VSCode的使用
java·开发语言·javascript
ch_s_t2 小时前
新峰商城之分类三级联动实现
前端·html
辛-夷2 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js