前端,把 api 封装到一个文件夹到底有没有必要 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
kensoz
V2EX    Javascript

前端,把 api 封装到一个文件夹到底有没有必要

  •  
  •   kensoz 2021-08-30 10:29:04 +08:00 6682 次点击
    这是一个创建于 1517 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在改一个 vue 项目,看了很多 api 接口封装的文章,似乎这是主流的处理方式?

    于是尝试了一下发现了几个问题。

    首先,管理是集中了,不过有点类似于 vuex 的思维方式。看过很多人不提倡把请求放在 vuex 里,而使用就近原则,不过本题的 api 接口封装,虽然只是把一些基本实例写好在导入到模板文件中,但是还是让人感觉到了 vuex 那个味。

    其次,修改起来感觉没有独立方便,导入还有改名什么的有点麻烦了,感觉 api 端口修改频率都不如参数的修改高。尤其是需要改名的时候,忘记改一个地方的情况经常发生。

    所以想问问,这种方案真的是主流的处理方式嘛?真的有必要嘛?

    34 条回复    2021-09-10 10:35:54 +08:00
    hackyuan
        1
    hackyuan  
       2021-08-30 10:34:49 +08:00
    选中函数名 F2
    acthtml
        2
    acthtml  
       2021-08-30 10:39:26 +08:00   1
    抽象出来,放在一个独立的文件夹主要解决:

    1. 多个前端组件可能对同一个 [后端服务复用] 。
    2. 组件不依赖于服务,而是依赖于服务的产生的数据,方便 [前端组件的复用] 。
    3. 结构清晰,如果后端服务改动,可以很容易做修改。
    zarvin
        3
    zarvin  
       2021-08-30 10:42:00 +08:00
    vue 开发全局搜索用得多,看了几个开源项目好像都是这样
    shzx1994529
        4
    shzx1994529  
       2021-08-30 10:46:51 +08:00
    一个路由一个 api.js 呗,这玩意无所谓啊,能让别人看懂就行
    Yooe
        5
    Yooe  
       2021-08-30 10:46:56 +08:00
    我怎么记得之前好像看到过这个帖子?= =
    Kusoku
        6
    Kusoku  
       2021-08-30 10:47:31 +08:00   1
    小的项目放在一起就是为了方便统一维护,根据模块拆分到不同的小文件也是为了按需要引入,这种组织方式可以适应绝大部分项目的需要。如果按模块划分子文件夹,然后再每个子文件夹再划分各种小文件夹,有部分公共请求也需要单独拿出来,实际上还不如单独用一个文件夹来放请求接口配置的内容,区分公共部分和模块部分,这样清楚明晰方便定位。
    chenluo0429
        7
    chenluo0429  
       2021-08-30 10:48:48 +08:00
    api 接口封装就是为了对组件屏蔽后端的具体实现,同时统一处理接口的变更。
    如果有参数变更,封装反而可以通过默认值等手段来快速适配。如果有破坏性的变更需要全部修改,封装的你都能忘记,独立的你就一定改得全?
    mozhizhu
        8
    mozhizhu  
       2021-08-30 10:49:04 +08:00
    你经历过后端改请求方式吗?经历过后端改路由吗?
    cydysm
        9
    cydysm  
       2021-08-30 10:54:19 +08:00
    不同项目有不同的处理方式,没有银弹,写得多了就知道怎么办了。
    netwjx
        10
    netwjx  
       2021-08-30 11:03:09 +08:00   4
    没有显著必要, 曾经多年 java 和 dotnet, 熟悉各种 MVC, ORM

    在前端领域, 将 ajax 调用抽取成函数, 99%是脱裤子放屁


    因为 ajax 请求的代码量很少, 后端是因为这部分业务逻辑比较多, 必须复用

    前端就算写 3 遍 ajax 调用, 重复的只有 1~2 行, 所谓封装, 只是将这部分重复的代码, 变成了重复的函数调用, 并没有实质意义, 反而增加了一层映射关系(函数名 -> ajax URL)


    不重复的部分是如何生成不同的 ajax 请求参数, 而切实有效的封装, 是真能减少重复业务逻辑的

    比如根据业务逻辑

    - 规整各种 ajax 请求参数: id 必须转换成有效数字, 而不能是字符串
    - 处理 ajax 响应参数: 转换成适合前端使用的格式

    另一类需要集中处理的逻辑, 用 ajax 模块插件实现

    - 认证和授权, 引导到登录界面, 申请权限界面
    - 异常处理, 上报和用户友好提示
    - 接口缓存
    - 并发控制
    libook
        11
    libook  
       2021-08-30 11:07:50 +08:00
    抛开场景谈对错都是耍流氓。

    一个 Vue 项目是由各种类别的模块组成的(如组件、服务、路由……),对于不同项目来说;不同类别的模块规模不同,比如可能营销项目路由路由巨多、组件和服务很少,后台项目路由很少、组件和服务巨多;模块之间的对应关系也不一样,比如有的项目服务可以被所有组件复用,也有的项目服务是从属于各个组件的,不可跨组件使用。
    Vue 提供了项目文件结构的灵活性,那么自然在不同场景下可以设计成不同的文件结构,最终都是服务于开发效率。
    Biwood
        12
    Biwood  
       2021-08-30 11:32:24 +08:00   1
    我也觉得有些多余,明明 request 工具在组建里面直接请求就行了,还要多封装一层,而且这些请求函数复用性特别低,绝大部分都只在一个模块用到了一次而已。真有复用的情况,也是跟着组件一起,而不是单独调用。有种过度设计的感觉。

    @libook
    楼主说的应该是那种纯粹的 api 封装,只是提前固定了路径和 method 而已,不携带具体的业务数据,你说的“服务”的概念其实已经在 api 基础上做了其他业务逻辑了。
    lin07hui
        13
    lin07hui  
       2021-08-30 11:32:46 +08:00
    我这边之前的人封装的
    // src/api/index.js
    export * from "./xxx.js";

    // src/api/xxx.js
    export const xxxDetail = (id) => { ... };
    export const xxxList = ({ page, ... }) => { ... };
    export const xxxSave = ({ id, ... } ) => { ... };

    // src/store/modules/xxx.js
    import { xxxDetail, xxxList, xxxSave } from "@/utils/api";
    import { createState } from "@/utils/state";

    const optiOns= createState({ detail: { api: xxxDetail, ... }, list: { api: xxxList, ... }, save: { api: xxxSave, ... });

    export default {
    namespaced: true,
    ...options
    };

    // src/utils/state -- 248 行代码
    // createState
    /**
    * Create a store options
    * @param {Promise} list.api - 获取分页列表数据接口
    * @param {Object} list.params - 发送给接口的参数
    * @param {Function} list.beforeSet - 设置数据之前执行,beforeSet(接口数据)
    * @param {Promise} detail.api - 获取单条数据接口
    * @param {Object} detail.params - 发送给接口的参数
    * @param {Promise} save.api - 保存接口
    * @param {Function} save.success - 保存成功后运行:success({ commit, state, dispatch }, record)
    * ....还有很多
    */
    Biwood
        14
    Biwood  
       2021-08-30 11:34:20 +08:00
    当然,如果是 TypeScript 项目,这样写比较方便提前约定前后端字段的对应关系,类似于 adapter 的概念
    lin07hui
        15
    lin07hui  
       2021-08-30 11:36:11 +08:00
    @lin07hui #13 import { xxxDetail, xxxList, xxxSave } from "@/api";
    ansenJ
        16
    ansenJ  
       2021-08-30 11:36:24 +08:00
    初期多人开发的时候 别说 API 了 Store Router 都是每人一个文件, 等项目稳定了再汇总的。多人操作一个文件的那位, 你是爱上解决 git 冲突吗?
    fernandoxu
        17
    fernandoxu  
       2021-08-30 13:35:14 +08:00
    按领域组织更好,
    Sapp
        18
    Sapp  
       2021-08-30 14:15:41 +08:00
    现在 api 不都是自动生成函数么?还有人手写???
    chairuosen
        19
    chairuosen  
       2021-08-30 14:21:03 +08:00   3
    说不需要的都没写过大项目?
    cloudzqy
        20
    cloudzqy  
       2021-08-30 14:50:31 +08:00
    现在基本 typescript 了,基本都封,定义类型比较方便。
    kennhuang
        21
    kennhuang  
       2021-08-30 15:11:35 +08:00 via iPhone
    写 unit tests 的同学就知道封装出来的好处了
    hitaoguo
        22
    hitaoguo  
       2021-08-30 15:35:07 +08:00
    我用装饰器做的封装,代码类似这样。


    然后页面里就直接这样用
    ccraohng
        23
    ccraohng  
       2021-08-30 16:02:50 +08:00
    api 配置对象配合 ts 输出 一个类似 service 的对象
    chairuosen
        24
    chairuosen  
       2021-08-30 16:35:37 +08:00
    不封在一起,所有请求通用的:鉴权、域名配置、封装协议解构、异常判断、日志、重试、超时、token 过期重拿、mock 、监控、、、、、等等等等写在哪里。后端换个域名、path 、封装协议,甚至接口实现要所有地方挨个找么?

    按道理,所有非你项目代码实现的部分,都不应该直接在业务逻辑里调(比如 http 接口,RN 里的客户端接口,SDK 的接口),应该有个 adapter 层来做对接,都是不可信的,没准哪天对方心情不好想改就改的,他改了你不可能全局搜代码改吧。
    otakustay
        25
    otakustay  
       2021-08-30 17:51:13 +08:00   2
    masterV
        26
    masterV  
       2021-08-30 17:58:03 +08:00
    同意 2L acthtml 和 10L ntwjx 的答案。
    封装请求不仅仅是为了看起来工整有序,分离数据源与业务逻辑也很重要。
    封装请求后,前端业务处不 care 数据怎么来的、后端需要什么参数,通通可以在请求文件里写好,比如可做以下处理:

    1 、规范协议(请求参数、响应参数);
    2 、做默认赋值处理( page/size/offset/limit );
    3 、处理成后端需要的格式(指定类型、指定格式);
    4 、字段映射(后端字段命名可能是下划线,前端可能习惯用驼峰,前后端对同一事物翻译可能不同,映射了就皆大欢喜)
    5 、做好以上后,在 vscode 中会自动匹配到定义的参数类型给出提示,js 写成 ts 的感觉,很舒服,这应该就是上面很多楼提到的 adapter 的功能,顾名思义转接器。
    5 、其他:
    - 认证和授权, 引导到登录界面, 申请权限界面
    - 异常处理, 上报和用户友好提示
    - 接口缓存
    - 并发控制
    AV1
        27
    AV1  
       2021-08-31 01:59:37 +08:00
    ts 把 api 封装好,每次调用就能获得 interface 定义好类型的对象,不舒服吗?
    ae86
        28
    ae86  
       2021-08-31 09:48:04 +08:00
    @otakustay #25 有原地址吗,这种 狭长 的在电脑上看不方便
    liaoberlin
        29
    liaoberlin  
       2021-08-31 10:09:54 +08:00
    这个肯定要结合使用场景来讨论的,结合我这几年做过的项目倒是觉得统一在一个文件或文件夹下是比较合理的,说下我的使用方式

    1. 我们接口调用都不是手写的,通过一个命令行就直接生成到项目里面统一的文件夹了,包括接口配置跟接口方法、TS 声明,放在一起是方便统一生成,调用的地方只需要调用生成好的方法传参数就行

    2. 对于一些接口需要多个地方调用的,最好是要封装的,当然除了简单的 method,url 之外,有些接口还要一些 headers 、是否展示 loading 、是否是 restful 接口等等这些额外的配置参数,封装起来之后多处调用的时候都不需要管这些配置,只需要传参数就行

    3. 对于都放在同一个文件容易冲突,可以把一个接口或一类接口放在同一个文件,然后都在 api.js 中引入统一暴露出去就行
    otakustay
        30
    otakustay  
       2021-08-31 11:57:44 +08:00
    @ae86 找不到啥服务能托管 markdown 分享,就截成图了
    EscYezi
        31
    EscYezi  
       2021-09-01 13:34:45 +08:00 via iPhone
    @otakustay gist
    CQYJ
        32
    CQYJ  
       2021-09-01 13:59:17 +08:00
    封装是为了解决复用的问题,不复用就不要封装
    shilianmlxg
        33
    shilianmlxg  
       2021-09-02 17:48:21 +08:00
    @hitaoguo 大佬 求教程
    hitaoguo
        34
    hitaoguo  
       2021-09-10 10:35:54 +08:00
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2387 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 11:08 PVG 19:08 LAX 04:08 JFK 07:08
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86