原諒我是個標題黨,不過仔細看完這篇文章,一定會有所收穫的!
本篇文章主要目的是把前端的請求方式做一個極度精簡和自動化,給我們繁重的搬磚生活,帶來一點幸福感^_^。
相信跟我一樣的前端狗早就寫煩了各種請求,寫煩了各種請求路徑,大家在專案中請求的方式都各不相同,但是大致的方式都是差不多的。來看看以下大家常用的請求方式弊端分析,看看是不是也有類似的痛點,以vue + axios專案為例。
# 通用的檔案結構
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;
複製程式碼
這個檔案,在大家的專案裡應該都是存在的,主要用於請求中的公共配置和異常捕捉。
// 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種請求方式呢?這裡標記為痛點一
,我們在下面解決。
// test.vue
import * as http from '@/path/to/http'
export default {
methods: {
getData(){
https.get('/v1/systemInfo').then(res => {
// do something
})
}
}
}
複製程式碼
這就是我們呼叫時候的樣子,看起來也很規範了。但是大家試想一下,如果後端對api做了批量的修改呢。如果每個介面呼叫都散落在每個元件檔案裡,我們是不是要渠道每個檔案裡取對它們逐一修改,維護起來就很繁瑣了。還有每次都要去檢視api文件(痛點二
)要呼叫的介面路徑(痛點三
)和請求方式(痛點四
)是什麼,然後複製到業務頁面,每次我們在做這樣的事情的時候心裡是不是在默默罵娘,TMD怎麼這麼多介面要寫???
解決方案
上面說了一大堆廢話,說了那麼多問題。特麼要是沒拿出個好看的方案,勞資...,各位彆著急,聽..聽我慢慢說...
目錄結構
http
|--apiModules
| |--user.js
| |--system.js
|--parse
| |--parse.js
| |--api.json
|--fetch.js
|--config.js
複製程式碼
這就是我們的目錄結構,下面我就逐一介紹
如何解決痛點一?
為了避免繁瑣的封裝已有的請求方法,我們可以寫一個方法去實現,傳入什麼請求方式,就呼叫axios物件的什麼方法。引數的傳遞方式寫在判斷裡,這樣就避免了我們要用到什麼方式,就需要去封裝一個什麼請求方法。
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)
}
}
複製程式碼
所以我們的業務頁面程式碼變成了這樣:
// 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資料夾,這是一個錦上添花的檔案,如果恰好你的後端用了類似 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,歡迎大家參考使用。