原谅我是个标题党,不过仔细看完这篇文章,必定会有所收获的!前端
本篇文章主要目的是把前端的请求方式作一个极度精简和自动化,给咱们繁重的搬砖生活,带来一点幸福感^_^。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,欢迎你们参考使用。