这个前端Api管理方案会更好?(二)

前言

hello~ ,上周发了我的处女文《这个前端Api管理方案会更好?》,收获了"海量"支持和鼓励,也有一部分建议和质疑,一个个仔细看后也作了认真思考,希望这篇文章能够回应疑问和建议,感谢指点~

上篇回顾

还没看《这个前端Api管理方案会更好?》的可以先去看~~

总结几种API管理方案的优劣,提出Api文件面向模块 的方案,解决冗长文件带来的难维护性,可读性差 的问题,大大提升接口的可读性 。并通过封装函数统一对接口管理,提高代码的拓展性,灵活性和复用性

接下来对评论区的一些问题进行解答~~~~~~~

为什么导出的是一个封装函数api?

js 复制代码
// api映射表
const apiMap = {
    // 公共接口
    common: {
        commonFun1,
        commonFun2,
    },
    //对象1
    dog: {
        //增删改查
        add: obj1Func1
        get: obj1Func2
    },
    //对象2
    cat: {
        //增删改查
        upd: obj2Func3,
        del: obj2Func4,
    },
    ...
}

// 暴露一个访问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. 统一给接口加上固定参数

js 复制代码
// 偏函数固定参数,这里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')

芜湖,接口调用不需要参数啦?!

2. 统一给get列表接口加上本地缓存回显判断

希望给该模块所有列表页,统一新增本地缓存列表的筛选项功能

js 复制代码
export function getApi(obj, storageKey, params) {
    // 若带参数,则将参数缓存至本地
    if(params) {
        localStorage.setItem(storageKey, JSON.stringify(params))
        return apiMap[obj]['get'](params) 
    }
    
    // 若不带参数且本地有缓存的筛选项,那么直接取出并作为请求的params
    let  params = { page: 1, pageSize:20 }
    const storageParams = localStorage.getItem(storageKey)
    if(storageParams) params = JOSN.parse(storageParams)
    return apiMap[obj]['get'](params) 
}

// 使用
import { getApi } from "xxxmodule"
// 页面初始化列表
api('dog', this.storageKey)
// 页面获取列表
api('dog', this.storageKey, params)

芜湖,一个函数给n个列表页加上本地缓存功能?!

3. 统一给get列表接口加上节流

js 复制代码
// 暴露一个访问api映射表的函数, 参数是对象和操作
// 这里没有错误处理,jym看懂就行
export function throttleApi(obj, action, time) {
    const thrList = ['goodBoy', '爱吃好果汁丶']
    if (action) {
        if (action === "get" && thrList.includes(obj)) {
          return throttle(apiMap[obj][action], time); // 设置节流时间为 time 毫秒,期间返回空函数
        }
        return apiMap[obj][action]
    }
    // 返回一个包含多个操作接口函数的对象或公共接口
    return apiMap[obj]
}

芜湖,一个函数给n个get请求加上节流优化?!还能限制影响的对象!?

4. 统一让get请求同时拥有上面三种功能

js 复制代码
function -.-Api() {
    zhijixie
}

芜湖,一个函数......咦,怎么报错了?

因此我们可以根据场景选择导出封装函数,很好的解决了拓展性和复用性的问题

js 复制代码
import { paramsApi, getApi, throttleApi } from 'xxxmodule'

如果换做面向对象,想在模块里统一添加上述功能可以试想一下多麻烦

这个方案不利于Tree shaking?

Tree shaking:当使用 ES6 模块语法时,模块的导入和导出关系是静态的,这意味着在编译时可以确定模块之间的依赖关系。Tree shaking 利用这个特性,通过静态分析代码,识别出未被使用的模块、函数、变量等,并将其从最终的打包结果中删除。

是的,Tree shaking需要在编译时确定模块之间的依赖关系,而导出的api函数通过参数访问apiMap映射表,只有运行时才能确定依赖关系,即api函数是动态函数因此不利于Tree shaking

但实际上客观来说是可以接受的。

首先接口基本都是会被引用的,不存在被摇树去除的情况,因此对打包总体积没有影响

影响的是动态路由加载的页面,首次加载页面会把不需要的接口函数一起请求加载

在这个chunk里模块里的接口都被打包引入了

但api文件占的大小是多少呢?

这个模块31个接口函数,原 7.0k 压缩后4.7kb,在chunk里1%都占不到,即便300个接口,估计也就占个5%

因此带来的问题基本可以忽略不计,膈应的话(不建议 )可以使用拆模块,对象导出或者webpack优化降低影响。

拆模块

将当前模块的对象作模块化拆成更小的二级模块,但如果接口或对象不多不建议拆,避免大量文件->大量文件夹

对象导出

js 复制代码
export default {
    commonApi: apiMap['common'],
    dogApi: apiMap['dog'],
    catApi: apiMap['cat'],
}

import {commonApi,...} from "xxx"

严格说导入带有多个属性的对象也不利于Tree shaking, 无引用的接口函数也会被打包,但影响基本是忽略不计中的忽略不计,另外也无法直接使用封装函数。

webpack优化

js 复制代码
config.optimization.splitChunks({
    chunks: 'all',
    cacheGroups: {
        ...
        api: {
           name:'chunk-apis'    
           test: /api(/|\).*/index.js/,
           priority: 10,
           minChunks: 2,
        }
    }
})

api文件作为独立的chunk块,从而更好地利用加载缓存,其他页面引用api文件时就不会再重复加载了。

使用类继承

js 复制代码
class api {
    constructor(name) { 
        this.name = name; 
    } 
    get() { 
        return axios.get(`/${this.name}/get`)
    }
    add() { 
        return axios.get(`/${this.name}/add`)
    }
    upd() { 
        return axios.get(`/${this.name}/upd`)
    }
}

class dogApi extend api {
    constructor(name) { 
        super(name);
    } 
    bark() { 
        return axios.get(`/${this.name}/bark`)
    }
}

const dog = new dogApi('dog')
dog.get()
dog.bark()

类继承的方式是很棒的设计,非常适合crudrestful 规范的场景,可以较好的提高代码复用性,也方便对对象进行管理维护。

但是从方案的可读性上看,类依旧不如apiMap直观,因为接口实际上只需要关注接口名以及注释,而类则混入了逻辑代码,没有apiMap那么聚合。

并且当脱离crudrestful后,复用性的优势也没有了。

因此这个方案还是更推荐apiMap的方式。

应用场景

我个人认为小型到中大型无论是否crud都适用,很大规模的项目没接触过,但与其遵循什么低耦合高内聚,职责单一原则整一大长串文件没人看,还不如一把梭至少还能看的懂,设计原则的最终目的应该是面向开发者

模块里的对象太多怎么处理

答:拆成二级模块,模块文件夹下新增二级模块文件夹,保持apiMap的二级结构, 也能降低Tree shaking的影响

问:又太多了

答:继续拆

问:又又又又太多了,还要拆出去,你这方案也不怎么样嘛?还不如我左低耦合高内聚,右职责单一原则,单独export

答:...... 可以,重构!

问:看着5个二级模块,近100个对象陷入沉思......

前端小白记录思考点点滴滴,点赞收藏关注一下我呗(QwQ)~

相关推荐
cs_dn_Jie22 分钟前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic1 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿1 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具2 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test3 小时前
js下载excel示例demo
前端·javascript·excel
Yaml43 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事3 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶3 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo3 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx