原諒我是個標題黨,不過仔細看完這篇文章,必定會有所收穫的!前端
本篇文章主要目的是把前端的請求方式作一個極度精簡和自動化,給咱們繁重的搬磚生活,帶來一點幸福感^_^。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
})
}
}
}
複製代碼
咱們再來回顧一下:
痛點一
)痛點二
)痛點三
)痛點四
)痛點一
可能你們不必定都存在,那麼二三四
應該是通病了,也是本文主要想解決的。爲了避免每次都要翻看文檔的請求方式,請求路徑。做爲一個標準的前端配置攻城獅
咱們能夠把這些信息統一配置起來,就避免了每次都去查看的煩惱。咱們關心的應該是返回的數據格式和傳入的參數等,設想一下咱們每次這樣發請求該有多幸福啊!
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();
複製代碼
經過以上方法,優雅地解決了 二三四
三個痛點!
最後來講說咱們的parse文件夾,這是一個錦上添花的文件,若是剛好你的後端用了相似 swagger 或 postman 等能夠導出結構化的文件給你,好比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,歡迎你們參考使用。