也許這是最優雅的前端請求方案

原諒我是個標題黨,不過仔細看完這篇文章,必定會有所收穫的!前端

本篇文章主要目的是把前端的請求方式作一個極度精簡和自動化,給咱們繁重的搬磚生活,帶來一點幸福感^_^。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,歡迎你們參考使用。

相關文章
相關標籤/搜索