也许这是最优雅的前端请求方案

原谅我是个标题党,不过仔细看完这篇文章,必定会有所收获的!前端

本篇文章主要目的是把前端的请求方式作一个极度精简和自动化,给咱们繁重的搬砖生活,带来一点幸福感^_^。vue

相信跟我同样的前端狗早就写烦了各类请求,写烦了各类请求路径,你们在项目中请求的方式都各不相同,可是大体的方式都是差很少的。来看看如下你们经常使用的请求方式弊端分析,看看是否是也有相似的痛点,以vue + axios项目为例。node

# 通用的文件结构
request
|-- config.js
|-- http.js
复制代码
// config.js,主要对项目中请求的异常捕捉,添加配置等
import Vue from "vue";
import axios from "axios";
import { Notification } from 'element-ui';
import store from "@/store";

// 配置 Content-Type
axios.defaults.headers.post["Content-Type"] = "aplication/json";

/** * 配置 axios */
// http request 拦截器
axios.interceptors.request.use(
    config => {
        return config;
    },
    err => {
        return Promise.reject(err);
    }
);

// http response 拦截器
axios.interceptors.response.use(
    response => {
        // 对某些错误代码进行判断
        if(response.data.code == 2){ // identity failure
            store.dispatch("LogOut");
        }
        if (response.data.code !== 0 && response.data.msg !== -1) {
            Notification({
                title: '系统错误',
                message: response.data.msg,
                type: "error",
                offset: 35,
                duration: 2000
            });
        }
        return response;
    },
    error => {
        console.log(error);
    }
);

export default axios;
复制代码

这个文件,在你们的项目里应该都是存在的,主要用于请求中的公共配置和异常捕捉。webpack

// http.js,对config完成的axios进行封装
import axios from config
export function get(url, payload){
    return axios.get(url, {
        params: payload
    })
}

export function post(url, payload){
    return axios.post(url, {
        params: payload
    })
}

// export function delete...
复制代码

这个文件主要对axios的经常使用方法作一些封装,主要为了参数传递方式一致,调用时能够不用写axios. 等,总之这样就有了一个统一的入口,调用时更加方便。这样有它的好处,可是有没有更好的解决方案呢?若是还有 DELETE PUT这些请求方式呢?若是有10种请求方式呢?这里标记为痛点一,咱们在下面解决。ios

// test.vue
import * as http from '@/path/to/http'

export default {
    methods: {
        getData(){
            https.get('/v1/systemInfo').then(res => {
                // do something
            })
        }
    }
}
复制代码

这就是咱们调用时候的样子,看起来也很规范了。可是你们试想一下,若是后端对api作了批量的修改呢。若是每一个接口调用都散落在每一个组件文件里,咱们是否是要渠道每一个文件里取对它们逐一修改,维护起来就很繁琐了。还有每次都要去查看api文档(痛点二)要调用的接口路径(痛点三)和请求方式(痛点四)是什么,而后复制到业务页面,每次咱们在作这样的事情的时候内心是否是在默默骂娘,TMD怎么这么多接口要写???git

解决方案

上面说了一大堆废话,说了那么多问题。特么要是没拿出个好看的方案,劳资...,各位别着急,听..听我慢慢说...github

目录结构

http
|--apiModules
|	|--user.js
|	|--system.js
|--parse
|   |--parse.js
|   |--api.json
|--fetch.js
|--config.js
复制代码

这就是咱们的目录结构,下面我就逐一介绍web

如何解决痛点一?

为了不繁琐的封装已有的请求方法,咱们能够写一个方法去实现,传入什么请求方式,就调用axios对象的什么方法。参数的传递方式写在判断里,这样就避免了咱们要用到什么方式,就须要去封装一个什么请求方法。shell

import axios from './config' // config文件仍是跟上面的同样,这里再也不说明
// fetch.js
export function fetch(method, url, payload){
    // 查看axios的文档,咱们知道除了get的传参方式不同,其他的都是直接传递,那么咱们只须要判断get就能够
    if(method === 'get'){
        return axios['get'](url, {params: payload})
    } else {
        return axios[method](url, payload)
    }
}
复制代码

因此咱们的业务页面代码变成了这样:npm

// test.vue
import fetch from '@/path/to/fetch'

export default {
    methods: {
        getData(){
            fetch('get','/v1/systemInfo', {...}).then(res => {
                // do something
            })
        }
    }
}
复制代码

这里看起来其实没什么变化,仅仅改了方法名而已。可是又发现了一个小问题,每次新建一个页面我都须要引用一次fetch吗?麻烦啊!因此能够直接把fetch方法挂载到vue实例上,那么在组件内部就能够直接调用了,又解决了一个小问题 ^ _ ^

// fetch.js
class Fetch {
    // 给Vue提供安装接口
    install(vue) {
        Object.assign(vue.prototype, {
            $fetch: this.fetch
        });
    }

    fetch(method, url, payload) {
      if(method === 'get'){
            return axios['get'](url, {params: payload})
        } else {
            return axios[method](url, payload)
        }
    }
}
export default new Fetch();

// main.js
import Vue from 'vue'
import Fetch from '@/path/to/fetch'
Vue.use(Fetch)

// test.vue
export default {
    methods: {
        getData(){
            this.$fetch('get','/v1/systemInfo', {...}).then(res => {
                // code
            })
        }
    }
}
复制代码

如何优雅地解决痛点二三四?

咱们再来回顾一下:

  • 请求方式的封装 (痛点一)
  • 每次都要去查看api文档(痛点二)
  • 要调用的接口路径(痛点三)
  • 查看请求方式(痛点四)

痛点一可能你们不必定都存在,那么二三四应该是通病了,也是本文主要想解决的。为了避免每次都要翻看文档的请求方式,请求路径。做为一个标准的前端配置攻城狮咱们能够把这些信息统一配置起来,就避免了每次都去查看的烦恼。咱们关心的应该是返回的数据格式和传入的参数等,设想一下咱们每次这样发请求该有多幸福啊!

this.$fetch('system.getVersion').then(res => {
    // code
})

/* * 你们的项目中,后端api确定都是区别了某个模块的,可能每一个模块作的人也不同 * 在调用的时候指定一下模块名,接口名就能够 * 不须要知道知道请求方式,请求路径 */
复制代码

要知足以上的需求,咱们确定须要用配置文件来记录以上信息,虽然咱们不用关心,可是程序是须要关心的!./apiModules 就是用来存放这些信息的。

// ./apiModules/system.js
export default {
    getVersion: {
        url: 'path/to/getVersion',
        method: 'get'
    },
    modVersion: {
        url: 'path/to/modVersion',
        method: 'post'
    }
}

// ./apiModules/user.js
export default {
    getInfo: {
        url: 'path/to/getInfo',
        method: 'get'
    }
}

// 固然,以上的配置字段均可以根据需求自定义,好比同一apiName要根据用户角色调用不一样接口,只须要在fetch写上相应的判断就能够,很是方便!
复制代码

因此咱们又要修改一下fetch文件了

import axios from "./config";

// 根据 ./apiModules文件夹中 生成fetchCfg -- 实现方法 webpack-require.context()
// fetchCfg = {
// system,
// user
// };
const fetchCfg = {};
// 经过 require.context 可让webpack自动引用指定文件夹中的文件
// 咱们将它存到 fetchCfg 上以供 fetch 方法使用
const requireContext = require.context('./apiModules', false, /\.js$/)
requireContext.keys().forEach(path => {
    let module = path.replace(".js", "").replace("./", "")
    fetchCfg[module] = requireContext(path).default
})

/** * 解析参数 * 这个函数主要负责解析传入fetch的 module 和 apiName * @param {String} param */
const fetchParam = param => {
    var valid = /[a-z]+(\.[a-z])+/.test(param);
    if (!valid) {
        throw new Error(
            "[Error in fetch]: fetch 参数格式为 moduleName.apiName"
        );
    } else {
        return {
            moduleName: param.split(".")[0],
            apiName: param.split(".")[1]
        };
    }
};

class Fetch {
    // 给Vue提供安装接口
    install(vue) {
        Object.assign(vue.prototype, {
            $fetch: this.fetch
        });
    }

    /** * 对axios封装通用fetch方法 * 会根据传入的下列参数自动寻找 method 和路径 * @param {*} module 对应 fetch配置的名字 * @param {*} apiName 该模块下的某个请求配置名 */
    fetch(moduleInfo, payload) {
        let prefix = '/api'
        let moduleName = fetchParam(moduleInfo)["moduleName"];
        let apiName = fetchParam(moduleInfo)["apiName"];
        // 判断没有找到传入模块
        if(!fetchCfg.hasOwnProperty(moduleName)){
            throw new Error(
                `[Error in fetch]: 在api配置文件中未找到模块 -> ${moduleName}`
            );
        }
        // 判断没有找到对应接口
        if(!fetchCfg[moduleName].hasOwnProperty(apiName)){
            throw new Error(
                `[Error in fetch]: 在模块${moduleName}中未找到接口 -> ${apiName}`
            );
        }
        let fetchInfo = fetchCfg[moduleName][apiName];
        let method = fetchInfo["method"];
        let url = `${prefix}/${fetchInfo["url"]}`;

        if (method === "get") {
            return axios[method](url, {
                params: payload
            });
        } else {
            return axios[method](url, payload);
        }
    }
}

export default new Fetch();

复制代码

经过以上方法,优雅地解决了 二三四 三个痛点!

锦上添花的api配置文件解析脚本

最后来讲说咱们的parse文件夹,这是一个锦上添花的文件,若是刚好你的后端用了相似 swaggerpostman 等能够导出结构化的文件给你,好比json,而后你经过简单的node脚本转化就能够获得以上的api配置信息,一旦后端修改了api,咱们再run一遍脚本就能够把全部的更改应用到项目,而不须要手动修改api文件了,就算是须要手动修改,也不用在每一个业务文件中修改,方便快速~

如下是我读取api层postman文档的脚本,这里也能够有不少自动化的方式。好比这个文档被托管在了git,每次api更新文档以后,咱们能够预先写一段shell脚本,把git的更新同步到本地,而后启动node脚本(能够把命令放在package.json里的script标签中用npm调用)读取/写入文档。可能在第一次写脚本的时候有不会的地方,可是一旦写好,与后端小伙伴作好约定,以后的工做是否是快了不少呢?

// parse.js
/** * README * 读取中间层json文件,生成api配置 */

let fs = require("fs");
let path = require("path");
let dosJson = require("./api.json");

var jsFile = fs.createWriteStream(path.resolve(__dirname, "./api/ddos.js"), {
    encoding: "utf8"
});

function parsePostManJson(json) {
    Object.keys(json).map(key => {
        // 添加注释
        if (key === "name") {
            jsFile.write(`// ${json[key]}`)
            console.log(`// ${json[key]}`);
        }
        if(key === "request"){
            let urlName = json[key].url.path[json[key].url.path.length - 1];
            let url = json[key].url.raw.replace("{{HOST}}", "");
            let method = json[key].method;
            let params = "";
            if(method === "GET"){
                params = `// ${url.split("?")[1] ? url.split("?")[1] : ""}`;
                url = url.split("?")[0];
            }
            // let content = `${method === 'GET' ? params : ""}`
            let content = ` ${urlName}: { url: "${url}", method: "${method.toLowerCase()}", custom: true }, `
            console.log(content);
            jsFile.write(content)
        }
        if(key === "item" && json[key].constructor === Array){
            json[key].map(itemJson => {
                parsePostManJson(itemJson);
            })
        }
    });
}

jsFile.write(`export default {`)

parsePostManJson(dosJson);

jsFile.write(`}`)

jsFile.end();

jsFile.on('finish',function(){
    console.log('写入完成');
})

jsFile.on('error',function(){
    console.log('写入失败');
})
复制代码

输出的api文件,还添加了一些注释,若是有须要也能够直接把参数格式写入,之后就不用去打开线上文档查看了,是否是很方便呢?

// ddos.js
export default {
    // 获取ddos模式
    getDDosCfg: {
        url: "/getDDosCfg",
        method: "post",
        custom: true,
        napi: true
    },

    // DDos融入// 数据报表统计// 获取机房概览信息
    statisticsInfo: {
        url: "/admin/Ddos/Statistic/statisticsInfo",
        method: "post",
        custom: true
    }
};

复制代码

总结一下

哈哈,能耐心看到这里实属不易,你真是一个小棒棒呢~ (⊙﹏⊙)

So,在开发中,咱们尽可能要去思考一个问题,就是怎么让繁琐的事情变得简单化,在遇到繁琐重复性高的问题,要有去解决的想法。能用程序去完成的东西,咱们就尽可能不重复搬砖。这样既能够在繁忙的开发中得到一点幸福感,也可让咱们的coding能力慢慢提高~

以上的解决方式仅仅是一种思路,具体的代码实现上能够根据项目的框架、实际引用的请求库、业务需求来封装。固然,若是刚好你跟个人业务需求差很少,以上的代码能够知足业务,我把代码已经放到了github,欢迎你们参考使用。

相关文章
相关标签/搜索