最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 中大型项目中对网络请求(Axios)、Helper封装

    正文概述 掘金(熬夜的小青年)   2021-05-12   487

    前言

    接触Vue已经有几年了,每次新建项目都有一些新奇的想法,之前分享过一篇Vue中使用Axios拦截器(拦截请求与相应),由于我最近的项目需要长期维护,所以不能再向小项目那种方式创建前端架构了,需求需要方便维护、高扩展性,但不是说我最初的那种方式不好,只是不适合中大型项目而已。所以今天我分享一下我在中大型项目中封装Axios和Helper的方法。每个人的需求不同,抱着学习的态度,大家一起分享经验。所以废话不多说,直接上干货。

    项目目录

    首先是我的项目架构,由于我的项目分的比较细,我只展示部分和本文章相关的模块。

    |-----src 
    |   |-----common 公共文件
    |   |    |-----Api 后端接口
    |   |    |    |-----modules 接口模块
    |   |    |    |    |-----User.js 用户接口模块
    |   |    |    |-----index.js 公共接口模块
    |   |    |-----Helper 公共Helper
    |   |    |    |-----index.js 公共Helper
    |   |    |    |-----.....js 其他公共方法抽离
    |   |-----sevice 网络请求封装(axios封装)
    |   |    |-----api.js 请求方式封装
    |   |    |-----request.js axios封装
    |   |-----utils 工具类
    |   |    |-----vue-install.js vue全局绑定工具类
    |   |-----main.js webpack入口文件
    

    Api后端接口

    为什么要把接口单独抽离出模块呢?是因为在中大型项目中后端可能使用saas平台架构,拆分出不同模块,为了方便维护,快速查找定位接口,我们就需要将单文件进行解耦,抽离相同模块方便维护。当然如果后端没有用saas平台架构或接口较少的话,可以不使用这种方式直接写到index.js中这些都是根据个人需求去改变的无需纠结。

    // index.js
    import User from './modules/User'
    
    export default {
    
      User, // 将独立的模块抽离到单独的js文件中可以方便统一维护
    
      // 将公共的接口抽离出来方便维护 method:请求方式 url:接口地址
      sendSms: { method: 'post', url: '' },
    
      uploadFile: { method: 'post', url: '' }
    }
    
    

    // User.js

    export default {
        getUser: {method: 'get', url: ''}
    }
    

    公共Helper

    同理,helper也可以抽离出不同的方法,方便维护。

    // index.js
    import deepClone from './deepClone'
    import FormatDate from './FormatDate'
    
    const Helper = {
    
        /**
         * 深度拷贝
         * @param {object} 对象
         * @return {object}
         */
        deepClone,
    
        /**
         * 日期格式化
         * @param {date} timestamp 日期/时间戳
         * @param {string} format 格式 默认值 Y-m-d
         * @return {string}
         */
        FormatDate,
    
        /**
         * 获取指定日期之间的所有日期
         * 日期格式 yyyy-MM-dd
         * @param {string} start 开始日期
         * @param {string} start 结束日期
         * @return {array}
         */
        getAllDate,
    
        /**
         * 随机字符串
         * @param {boolean} lower 小写字母
         * @param {boolean} upper 大写字母
         * @param {boolean} number 数字
         * @param {boolean} symbol 特殊字符
         * @param {boolean} length 长度
        */
    
        RandomString,
    
        /**
         * 计算请求分页参数
         * @param {object} route 当前页面路由
         * @return { limit: 10, page: 1, offset: 0 } limit = 长度, page = 页码, offset = 偏移量
         */
        getPerPage (query) {
            let limit = parseInt(query.limit) || 10,
                page = parseInt(query.page) || 1,
                offset = (page - 1) * limit;
            return {
                limit, page, offset
            }
        },
    
        /**
         * 清理对象
         * 去除属性值为 空(字符串), null, undefined
         * 转换值为数字,true,false的字符串为对应的数据类型
         * @param {object} obj 对象
         * @return {object}
         */
        clearObject (obj) {
            let o = {};
            for (const k in obj) {
                let v = obj[k];
                if (v === null || v === undefined) continue;
                // 非字符串
                if (toString.call(v) !== '[object String]') {
                    o[k] = v;
                    continue;
                }
                v = obj[k].trim();
                // 过滤空值
                if (v.length === 0) continue;
    
                // 正数,负数,浮点数
                if (/^(-?\d+)(\.\d+)?$/.test(v)) {
                    o[k] = Number(v);
                }
                // 布尔值
                else if (v === 'true' || v === 'false') {
                    o[k] = (v === 'true');
                }
                // false
                else {
                    o[k] = v;
                }
            }
            return o;
        }
    
    }
    
    
    export default Helper;
    

    由于有些代码比较长我这里只分享一部分模块。

    // 深度复制
    const deepClone = (obj) => {
        let o;
        if (typeof obj === 'object') {
            if (obj === null) {
                o = null
            } else {
                // 数组
                if (obj instanceof Array) {
                    o = [];
                    for (const item of obj) {
                        o.push(deepClone(item))
                    }
                }
                // 对象
                else {
                    o = {};
                    for (const j in obj) {
                        o[j] = deepClone(obj[j])
                    }
                }
            }
        }
        else {
            o = obj;
        }
        return o;
    }
    
    export default deepClone;
    

    到此我的common公共文件就完成了,其实中心思想还是将之前的单文件抽离出不同的模块,这样方便后面扩展。接下来是网络请求的封装。这里可能比较绕,不过仔细看过后还是能理解的。

    sevice 网络请求封装(axios封装)

    // api.js
    import Api from '@/common/Api'
    import Helper from '@/common/Helper'
    import axios from './request'
    
    const axiosApi = {
      // get请求
      createGet (url) {
        return (args = {}) => {
          return axios({
            method: 'GET',
            url: url,
            ...args
          })
        }
      },
    
      // post请求
      createPost (url) {
        return (args = {}) => {
          let parmas = ''
          /*
            不挂载到url地址上 为什么我这里要这么写呢,
            因为后端post接收参数有一部分是从地址栏获取,
            虽然我很诧异但是我直接给他两份他随便获取,
            当然可以根据notMountUrl参数选择不绑定到地址栏
            */ 
          if (!args.notMountUrl) {
            parmas = `?${Helper.formatParams(args.data)}`;
          }
          return axios({
            method: 'POST',
            url: `${url}${parmas}`,
            ...args
          })
        }
      },
    // .... 当然你也可以写更多请求方式或者根据不同的需求调用不同的Axios封装
    
      // 创建API请求方法
      crateApi (apiConfig) {
        let methods = {}
        // 获取所有api的Key值进行循环
        Object.keys(apiConfig).forEach(key => {
          let item = apiConfig[key]
    
          // 子集请求 判断是否是单独模块 如果是就递归子集
          if (!item.method && !item.url) {
            return methods[key] = this.crateApi(item)
          }
          // 接口动态创建
          const method = item.method.toLocaleUpperCase()
          if (method === 'GET') {
            methods[key] = this.createGet(item.url)
          }
          else if (method === 'POST') {
            methods[key] = this.createPost(item.url)
          }
        })
        return methods
      }
    }
    // 这里一定是抛出创建方法,如果你晕了可以从这一步往回走,慢慢你就懂了
    export default axiosApi.crateApi(Api)
    
    // request.js
    import axios from 'axios'
    // 这里我使用的是vant ui库可根据需求更换不同的ui库
    import { Toast } from 'vant';
    import store from '@/store'
    import router from '@/router'
    // statusCode 错误状态码 这里错误码其实就是将网络请求中的所有status进行了键值对的封装方便调用
    import statusCode from '@/common/BaseData/status_code.js'
    
    
    // 是否为生产环境
    const isProduction = process.env.NODE_ENV == "production";
    
    // 创建一个axios实例
    const service = axios.create({
      baseURL: !isProduction ? '/api' : process.env.VUE_APP_BASE_URL, // 前缀由于我使用的是webpack的全局变量,这里其实也可以写死
      withCredentials: true, // 当跨域请求时发送cookie
      timeout: 20000 // 请求超时时间
    })
    
    // 请求拦截器
    service.interceptors.request.use(
      config => {
        // 获取token
        const hasToken = store.getters.Token
    
        // 设置 token设置token
        if (hasToken) {
          config.headers['token'] = hasToken
        }
        // .... 其他操作根据具体需求增加
        return config
      },
      error => {
        // 处理请求错误
        // console.log(error)
        return Promise.reject(error)
      }
    )
    
    // 响应拦截器
    service.interceptors.response.use(
    
      /**
       * 通过自定义代码确定请求状态
       */
      response => {
        const res = response.data
        // 这里的error是后台返回给我的固定数据格式 可根据后端返回数据自行修改
        const { error } = res;
        // error不为null
        if (error) {
    
          // 弹出报错信息
          Toast.fail({
            icon: 'failure',
            message: error.msg
          })
    
          // 后端返回code的报错处理
          switch (error.code) {
            case '1000':
            case '1001':
            case '1002':
            case '1003':
              router.replace({ path: '/error', query: { code: error.code, msg: error.msg } })
              break;
            case '6000':
            case '6100':
              // 清空Token 重新登录
              store.dispatch('user/resetToken')
              return Promise.reject(new Error(error.msg));
            case '6200':
            case '7000':
            case '19000':
            default:
              // 如果状态码不是 则判断为报错信息
              return Promise.reject(new Error(error.msg))
          }
        } else {
          // 正常返回
          return res
        }
    
      },
      error => {
        // 这里就是status网络请求的报错处理 主要处理300+ 400+ 500+的状态
        console.error('err:' + error)
        // 弹出请求报错信息
        Toast(statusCode[error.statusCode])
        return Promise.reject(error)
      }
    )
    // 向外抛出
    export default service
    

    service网络请求封装结束,这里主要的是api.js的封装比较绕,其实就是动态创建后端接口的方法,万变不离其宗,都是换汤不换药的写法

    utils工具类

    最后的工具类其实就是将我们所有的方法绑定到vue的prototype原型上,以此形成完整的闭环 如果不了解vue绑定插件的机制的话,可以去vue.js官网自行查找,其实没多难。

    // vue-install.js 全局绑定
    import Api from '@/service/api'
    import Helper from '@/common/Helper'
    
    export default {
      install: (Vue) => {
        // 全局注入 $api 请求 将我们api.js抛出的方法绑定到vue实例上
        Vue.prototype.$api = Api
        // 全局注入 $helper 辅助方法 同理将helper公共方法绑定到vue上
        Vue.prototype.$helper = Helper
      }
    }
    

    工具类的绑定结束,致辞我们就差最后一步就完成我们的闭环了,那就是引入到main.js中

    main.js webpack入口文件

    import Vue from 'vue'
    
    import router from './router'
    import store from './store'
    // .... 其他依赖引入
    import vueInstall from './utils/vue-install' // 全局注册
    
    
    // 全局绑定 直接使用use方法绑定
    Vue.use(vueInstall);
    
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')
    

    使用方法

    // 某页面
    async created () {
    
        // 请求User模块
        const res = await this.$api.User.getUser({ 
        data: { 
            // 参数.....
        },
        // 选择是否挂载到url上 
        notMountUrl: false
            
        })
        // 深拷贝
        const newResult = this.$helper.deepClone(this.result)
      }
    
    

    大功告成,这就是目前我项目中网络请求与helper公共方法的封装。希望对大家有所帮助,同时也虚心接受大家的各种建议和意见。希望大家在评论区进行评论。我会持续对我所掌握的知识进行分享。不忘初心坚持开源开放的学习态度。


    起源地下载网 » 中大型项目中对网络请求(Axios)、Helper封装

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元