这个前端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)~

相关推荐
拜晨3 分钟前
用流式 JSON 解析让 AI 产品交互提前
前端·javascript
浩男孩6 分钟前
🍀vue3 + Typescript +Tdesign + HiPrint 打印下载解决方案
前端
andwhataboutit?8 分钟前
LANGGRAPH
java·服务器·前端
无限大68 分钟前
为什么"Web3"是下一代互联网?——从中心化到去中心化的转变
前端·后端·程序员
cypking11 分钟前
CSS 常用特效汇总
前端·css
程序媛小鱼14 分钟前
openlayers撤销与恢复
前端·js
Thomas游戏开发15 分钟前
如何基于全免费素材,0美术成本开发游戏
前端·后端·架构
若梦plus17 分钟前
Hybrid之JSBridge原理
前端·webview
chilavert31818 分钟前
技术演进中的开发沉思-269 Ajax:拖放功能
前端·javascript·ajax
xiaoxue..19 分钟前
单向数据流不迷路:用 Todos 项目吃透 React 通信机制
前端·react.js·面试·前端框架