这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
简介
大家好,前端小白一枚,目前接触后台管理系统比较多,经常遇到不同对象的增删改查的接口,如何对Api进行一个有比较好的管理是个问题。在学习偏函数的时候有了灵感,想到一个不错的API管理方案,并应用在项目一个模块当中,并且开发效率和维护性可读性都很不错,和大家分享一下~
当前项目的前端API管理方案
// 封装的接口 export function obj1Func1(){} export function obj1Func2(){} export function obj2Func3(){} export function obj2Func4(){} // 引入方式 import { obj1Func1, obj1Func2, obj2Func3, obj2Func4 } from 'xxx' // 使用方式 const params = {...} await obj1Func1(params)
当接口多了之后,我们管理接口(查找)是一件很麻烦且废眼睛的事,需要一直翻,注释看不过来,维护性和可读性差。
统一export方式
// 封装的接口 function obj1Func1(){} function obj1Func2(){} function obj2Func3(){} function obj2Func4(){} // 导出接口 export { obj1Func1,//注释 obj1Func2,//注释 obj2Func3,//注释 obj2Func4,//注释 ... } // 引入方式 import { obj1Func1, obj1Func2, obj2Func3, obj2Func4 } from 'xxx' // 使用方式 const params = {...} await obj1Func1(params)
优点
- 面向对象,不需要每个接口函数都引入,开发时调用方便
- 简化操作类型命名,如
update -> upd
,开发和维护方便
缺点
- 当模块涉及的对象很多,则需要建立非常多的文件,当文件名复杂时,难以看懂维护难度up。
- 当页面需要引入多个对象,需要引入一个个文件,降低开发效率。
- 找对象需要拖拽到文件最底部
以面向模块(对象)的方式
假设当前模块下涉及到 n 个对象及对应的增删改查接口, 定义一个映射表
// api映射表 const apiMap = { // 公共接口 common: { commonFun1, commonFun2, }, //对象1 dog: { //增删改查 add: obj1Func1 get: obj1Func2 }, //对象2 cat: { //增删改查 upd: obj2Func3, del: obj2Func4, }, ... }
apiMap
对象
一级键名是模块涉及的对象
二级键名是对象相关的操作类型,值是对应的接口函数
导出方式1
直接导出各个对象
export default { commonApi: apiMap['common'], dogApi: apiMap['dog'], catApi: apiMap['cat'], } import {commonApi,...} from "xxx"
面向模块(多个对象)
本质就是以对象的方式来进行管理,只不过这里面向的是模块。这里一个模块只对应一个文件,包含了涉及到的n个对象的接口。因为我觉得一个模块下建n个对象一长串的Api
文件,又没法对文件名注释(文件名总有不认识或拼接的单词吧)只会带来更大的维护困难
找了几个不常见的英语名词,英语烂仔直接带上痛苦面具
而有了映射表就相当于有了一个目录(文件最上方一目了然, Map下的对象十分清晰还有注释),
至少目前我是能都秒读懂接口含义了
也不会出现面对老项目里那种长得拖不完的不知名接口文件的懵逼,点进去还只有几行代码(雪花飘飘~)
优点:
- 同上
apiMap
变成接口目录,可读性和可维护性提高(下方介绍)- 涉及同模块多个对象只需要引入一个文件
缺点:
- apiMap(目录)和export的位置一个在文件最上方,一个在最下方,浏览时非常不方便,依旧需要经常拖拽
这种方式已经比较好用了,从可维护性和可读性,拓展性来看我更推荐第二种方式
导出方式2
导出一个访问映射表的函数,参数是对象及操作类型,如(dog, upd)
// 暴露一个访问api映射表的函数, 参数是对象和操作 // 这里没有错误处理,jym看懂就行 export default function api(obj, action) { if (action) { // 返回某对象某操作的接口函数,如dogUpdate return apiMap[obj][action] } // 返回一个包含多个操作接口函数的对象或公共接口 return apiMap[obj] } // 封装的接口 function obj1Func1(){} function obj1Func2(){} function obj2Func3(){} function obj2Func4(){}使用时方式1
import API from 'xxx' // 没有错误处理,主打看懂 async function getData() { const params = {...} const data = await API(obj1, 'get')(params) await API(obj1, 'upd')(params) await API(obj1, 'del')(params) }使用时方式2
import API from 'xxx' const obj1API = API(obj1) const data = await obj1API.get(params) const data = await obj1API.upd(params) const data = await obj1API.del(params)
api
函数
api
函数可以复制或者写在公共模块引入就行了,实际上工作量只在维护映射表apiMap
现在查找接口原本一个模块里可能涉及10个对象共100个接口,顺序查找最差情况要看100条函数注释,而根据对象查找最差情况是10(对象)+10(操作类型)即20条函数注释。
const apiMap = { ... // 注释:这是target对象 targetObj: { add: objAddFunc, // 注释:增删改查的话可有可无 upd: objUpdateFunc, del: objDeleteFunc, get: objGetFunc, } ... }
只需要关注目标对象(其实是注释),清晰且一目了然,甚至不需要函数注释,不需要拖到文件底部
可维护性和可读性
模块化
这种方式用对象结构拆分也算是模块化了,看着不太习惯,但一个文件里对象和接口能都读懂,维护性和可读性也更好,即便接口函数再多行数再多,其实也只需要看apiMap
封装
(接下来开始胡扯~)
api函数
封装了一层,可以统一管理接口提高拓展性和复用性,例如统一给action
为get
的套一个节流函数
// 暴露一个访问api映射表的函数, 参数是对象和操作 // 这里没有错误处理,jym看懂就行 export default function api(obj, action) { if (action) { if (action === "get") { return throttle(apiMap[obj][action], 500); // 设置节流时间为 500 毫秒,期间返回空函数 } return apiMap[obj][action] } // 返回一个包含多个操作接口函数的对象或公共接口 return apiMap[obj] }或者当模块下有多个对象需要增删改查,且只需要一个参数id,那么只要再加个定制场景的
api函数
// 偏函数固定参数,这里constParams假设为 { id:007 } export function paramsApi(constParams, obj) { // otherParams是额外参数,在各自接口做个合并Object.assign() return (action, otherParams) => apiMap[obj][action](otherParams) } // 固定参数 const constParams = { id: 007 } const dogApi = paramsApi(constParams, 'dog') const catApi = paramsApi(constParams, 'cat') // 免参数直接调用即可 dogApi('get') dogApi('update', { color: 6 }) dogApi('delete') catApi('get') catApi('update', { color: 6 }) catApi('delete')
这个场景有些理想化,但有一层封装也确实能够在需要时方便统一管理
然后这里 action: objActionFunc
这里也算是一层封装,方便进行命名简写和规范
// xxxModule.js const apiMap = { //对象1 obj: { action: objActionFunc add: dogAdd, // xxx/dog/add mAdd: dogImport, // xxx/dog/import upd: dogUpdate, // xxx/dog/update }, }
开发体验
和同事沟通后,我应用在项目的一个模块中,感觉很棒!
首先写接口引入公共模块的api
函数,定义apiMap
映射表,正常写接口,工作量多了一个apiMap
罢了
// 引入api,getType函数 import { api } from "common" // api映射表 const apiMap = { ... } // 暴露api函数 export default api // 封装的接口 ...开发时查找接口就全程对着文件最上方的映射表复制,基本不需要怎么拖拽,不需要切换文件去找其他对象,也不需要看一堆无关代码,全程看注释,大大降低心智负担,小白上手也能分分钟找到
// xxxModule.js const apiMap = { //对象1 dog: { add: xxx get: xxx }, //对象2 cat: { upd: xxx, del: xxx, }, }引入接口时只要一个API函数,调用方式基本大差不差吧,反正都是
copy
,然后改下操作类型
import API from "@/api/xxx/xxxModule" // 调用时, 两种方式,复制个对象名,记住个操作类型,完事 const dogAPI = API('dog') const catAPI = API('cat') await dogAPI.get(params) await dogAPI.upd(params) await catAPI.get(params) // 或 await API('dog', 'get')(params) await API('dog', 'upd')(params) await API('cat', 'get')(params)
开发效率我觉得和对象方式的API管理方案没啥区别,都是copy
然后改下操作类型,但其实最大的好处还是在可维护性和可读性上
写在最后
非常感谢看到最后的jym,这是我本0前端小白通过偏函数产生的一点小想法,感觉挺好用的分享一下(可能场景比较简单局限后台系统),欢迎jym多多指点发表建议(玻璃心)