V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
giter
V2EX  ›  Vue.js

Vue 项目中,每个页面都有 2~3 个对话框,应该如何整理存放和复用?

  •  
  •   giter · 2023-12-03 11:09:05 +08:00 · 8710 次点击
    这是一个创建于 385 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前端基于 Vue3 + Element Plus + Vite + TypeScript 。当前,每个页面都有类似于:详情对话框、编辑对话框和各种嵌套对话框等,该复用的已经复用过了,剩下的对话框都含有各自的特色内容,不宜复用,强行复用可能会导致单文件代码量过多不利于后期维护。

    目前的疑问:我现在的做法是拆分每个 el-dialog 为单文件组件,存放到 components 文件夹下(可能有违 components 这个文件夹的定义,我的理解是 components 用来存放一些复用性很高的组件,而不是这种零散的对话框单组件文件)所以就导致我现在的项目结构很多 xxxDialog.vue 文件,既不知道如何归类存放,也不知道该怎么高效复用。

    73 条回复    2023-12-05 16:19:51 +08:00
    bsg1992
        1
    bsg1992  
       2023-12-03 12:12:16 +08:00
    组件只用于某一个页面,不需要再进行封装了。
    konakona
        2
    konakona  
       2023-12-03 12:15:55 +08:00
    收藏了,想看看大家的做法。
    giter
        3
    giter  
    OP
       2023-12-03 12:21:25 +08:00
    @bsg1992 那存放这些页面对话框,只能放到 components 文件夹下?我想用更优雅一点的方式,不然太多 xxxDialog.vue 文件了,看着碍眼
    owen800q
        4
    owen800q  
       2023-12-03 12:24:51 +08:00 via iPhone
    @bsg1992 不行。这样 template 太长了,不好维护
    giter
        5
    giter  
    OP
       2023-12-03 12:26:05 +08:00
    @owen800q 确实是,有些对话框的内容都好长了,全放在一个页面 template 下的话,动不动就上千行代码,确实很不利于后面维护
    owen800q
        6
    owen800q  
       2023-12-03 12:26:43 +08:00 via iPhone
    @giter 没办法。我们有三十多个 xxModal.vue, 一个 modal 都至少一百五十行代码, 不可以把他放在原来的页面,不然不好维护
    LandCruiser
        7
    LandCruiser  
       2023-12-03 12:28:11 +08:00   ❤️ 1
    components 文件夹下是放公共组件的地方,不能公用的组件不要往那放。
    比如商品浏览这个功能的所有组件,应该是在 src/views/GoodsList/目录下
    这个目录下应该有/GoodsItem.vue GoodsDetail.vue GoodsDialog.vue
    而不是把 GoodsDialog 放到/src/components/下去
    giter
        8
    giter  
    OP
       2023-12-03 12:28:16 +08:00
    @owen800q #6 难道只能这样滋生更多的 xxxModel.vue 或者 xxxDialog.vue 吗?太多类似文件感觉没充分复用,但强行复用又是另一个大麻烦
    LandCruiser
        9
    LandCruiser  
       2023-12-03 12:30:11 +08:00
    还有就是你没有必要为了抽象而抽象,为了提取组件而提取组件。提不提去组件不是按代码行数来的,同样几百行代码,有时候提出去方便,有时候写在一起方便。
    giter
        10
    giter  
    OP
       2023-12-03 12:30:12 +08:00
    @LandCruiser 有想过这个问题,谢谢,后面如果实在没有更好的办法,我就把对话框组件文件挪到 views 页面同级目录下,毕竟 components 只适合放通用性组件文件
    bsg1992
        11
    bsg1992  
       2023-12-03 12:34:57 +08:00
    @giter
    我的习惯是,基础组件以及业务类组件,放在 components 目录下。
    基础组件按照功能进行文件夹划分
    业务组件按照业务类型进行文件夹划分

    如果业务中出现了强依赖的组件,直接扔到该业务页面下建立一个文件夹进行管理。

    其实这种情况非常普遍,有一些业务功能看似能复用,
    但是他往往带有非常强的业务特有场景,这个时候你在想去复用他其实是没有意义的。
    bsg1992
        12
    bsg1992  
       2023-12-03 12:39:19 +08:00
    在工作中也看到一些同事,为了代码的复用,拆成 N 个组件,然后组件中甚至暴露了 N 多个 slot ,这样反而本末倒置,为了抽象而抽象。
    giter
        13
    giter  
    OP
       2023-12-03 12:39:50 +08:00
    @bsg1992 #11 那确实没得好的办法了,官方对 components 文件夹的定义是可复用性组件,对 views 文件夹的定义是可通过浏览器访问的页面( vue-router 下引入的组件文件来自 views 目录),所以如果没有好的归类地方,那最好的应该还是随 views 一起存放,外层用文件夹包裹以区分不同的页面内容
    giter
        14
    giter  
    OP
       2023-12-03 12:40:53 +08:00
    @bsg1992 #12 也不是为了代码复用而拆分,而是页面内容太多不利于维护,拆分后可以做到一个页面只完成一项或多项类似的功能,全部糅杂在一个页面真的很头疼,尤其是对话框很多的情况下
    zcf0508
        15
    zcf0508  
       2023-12-03 12:44:02 +08:00 via Android   ❤️ 2
    组件单独抽象一方面是复用,一方面是解耦,把对话框单独挪出去可以保证对话框内的逻辑独立,然后在主页面引用多个独立的对话框组件,是挺好的实践
    giter
        16
    giter  
    OP
       2023-12-03 12:46:44 +08:00
    @zcf0508 正确的
    lmqdlr
        17
    lmqdlr  
       2023-12-03 14:01:56 +08:00 via Android   ❤️ 2
    components 下面是公共组件,页面目录放一个 components 存放页面组件
    MrUser
        18
    MrUser  
       2023-12-03 14:44:40 +08:00   ❤️ 2
    这样?
    ./index.vue
    ./components/
    ./dialogs/
    ./list/index.vue
    ./list/dialogs/
    ./detail/index.vue
    ./detail/dialogs/
    humbass
        19
    humbass  
       2023-12-03 14:49:06 +08:00
    整不是应该抽离出来,写成 Promise 的方式调用吗?
    iOCZS
        20
    iOCZS  
       2023-12-03 14:58:42 +08:00
    从某种程度上讲,dialog 也是一个 route 。。。
    giter
        21
    giter  
    OP
       2023-12-03 15:16:52 +08:00
    @humbass 有了解过,但没深入了解,应该可行
    thevita
        22
    thevita  
       2023-12-03 15:28:18 +08:00   ❤️ 1
    本质应该算是后端的非主流半吊子前端来说下:

    我会习惯按业务逻辑相关性来放, 不通用 但 一起完成某个相关业务逻辑的放一个目录下
    如:

    ..../(some biz)/
    ..../(some biz)/list.vue
    ..../(some biz)/detail.vue
    ..../(some biz)/edit.vue
    ..../(some biz)/useHost.ts
    ..../(some biz)/popupList.vue
    ..../(some biz)/mergeToolDialog.vue
    ..../(some biz)/useBizLogic.ts -- ( 只是举个例子,一般还是会叫具体完成的事)
    giter
        23
    giter  
    OP
       2023-12-03 15:41:13 +08:00
    @thevita 看过多个开源的前端项目,都是这样的,最主要的页面取名 Index.vue ,剩下的如详情对话框,就取名 detail.vue ,编辑对话框就取名 edit.vue
    CHTuring
        24
    CHTuring  
       2023-12-03 15:42:27 +08:00   ❤️ 1
    不需要考虑那么多复用的情况,components 文件夹你只需要放公用的组件就好了,比如你在 Element Plus 的 Dialog 组件二次封装了组件。其它的页面内业务 Dialog 直接放在对应路由页面下的 components 文件夹或者不需要文件夹,直接 xxDialog.vue 或 xxModel.vue 就好了。
    ruoxie
        25
    ruoxie  
       2023-12-03 15:43:09 +08:00   ❤️ 1
    components 放业务无关的拆出去的组件,containers 放包含各种业务逻辑的拆出去的组件
    CHTuring
        26
    CHTuring  
       2023-12-03 15:49:59 +08:00   ❤️ 1
    1 、约束文件夹、文件的路径和命名规则
    2 、公共的 hooks 、utils 、组件的文档编写
    3 、异步引入减少加载时常和页面体积
    4 、测试(可选)

    只要规范这三个,不管是什么前端项目基本都清晰可维护了
    LavaC
        27
    LavaC  
       2023-12-03 15:56:37 +08:00   ❤️ 2
    对话框内容不多的话可以改成命令式调用的方法,传几个参数改改里面的字就够了。类似于 element 里的 this.$message 。
    如果各有业务内容的话那分成多个组件也是没有办法的事,不用内疚。
    业务特有的组件的话,我们公司的习惯是 views 下分页面,比如 foo 页面,如果 foo 页面下有专有组件就在 foo 文件夹下再建一个 components 文件夹,结构大概是这样
    views/
    foo/
    components/
    BarDialog.vue
    index.vue
    LavaC
        28
    LavaC  
       2023-12-03 16:11:44 +08:00   ❤️ 2
    @giter #21 不知道其他人是怎么解决的,我前几个月也尝试看看能不能将 dialog 改造成类似 model 一样的命令式调用,以减少页面上那一堆管理弹窗状态/传值的代码,现在用下来感觉还不错。
    giter
        29
    giter  
    OP
       2023-12-03 16:18:15 +08:00
    @LavaC #28 谢谢,有时间一定得去研究一下
    anguiao
        30
    anguiao  
       2023-12-03 18:42:56 +08:00
    我每个小模块会有自己的 components 文件夹,不是公用组件的话我就丢在这里面了。
    zogwosh
        31
    zogwosh  
       2023-12-03 19:42:55 +08:00
    还有一种方法,写一个文件叫 xxxModal.tsx,把这个页面的所有用到的 modal 文件放里面.
    nianyu
        32
    nianyu  
       2023-12-03 21:20:08 +08:00
    @giter 你的页面 dialog 样式 功能 回调都不一样,全靠传参反而更麻烦。一个模块用一个挺好的
    nianyu
        33
    nianyu  
       2023-12-03 21:21:38 +08:00
    你最后就算封装成一个组件了,该写的还是少不了,json 配置 table form 看过吗,其实你定义的 schema 跟直接写 html 没什么区别的。
    sjhhjx0122
        34
    sjhhjx0122  
       2023-12-03 21:21:45 +08:00   ❤️ 2
    弹出层这个事情也是为难了我很久,写到最后还是命令式调用,更符合直觉,你看看我写的这个库找找灵感 https://vue-modal-provider.netlify.app/
    totoro52
        35
    totoro52  
       2023-12-03 21:26:56 +08:00
    过度封装带来的后果是更难的维护和组件的膨胀
    giter
        36
    giter  
    OP
       2023-12-03 21:31:41 +08:00
    @sjhhjx0122 谢谢,我研究一下你的代码
    connection
        37
    connection  
       2023-12-03 21:57:26 +08:00   ❤️ 1
    首先弹窗组件的复用看功能,不从 ui 来划分复用。
    然后是弹窗声明式编写,但是命令式调用。
    TimPeake
        38
    TimPeake  
       2023-12-03 22:26:25 +08:00
    我最讨厌那种页面拆分成 N 个组件的了....有想过后面接手兄弟的感受吗?得看这 N 个组件的属性传递、自定义方法等,对了,vscode 默认还不支持点击跳转到该文件,来回跳转头都大了... 真不如塞到一个页面里 。什么, 页面代码太长?那是你注释不清晰....分块集中写在一起也很好阅读
    sjhhjx0122
        39
    sjhhjx0122  
       2023-12-03 23:18:36 +08:00
    @giter 我在写完这个库之后,就把弹窗当做页面,放在用弹窗的页面文件夹里,因为弹窗里面本身就承载像表单这种业务逻辑,只是他是命令调用不是当路由跳走的页面
    bojackhorseman
        40
    bojackhorseman  
       2023-12-03 23:37:49 +08:00 via iPhone
    我是拆组件,编辑和添加用一个组件,用 type 来区分,如果详情也是弹窗,相同的部分多的话就共用。
    zbowen66
        41
    zbowen66  
       2023-12-04 01:14:24 +08:00
    @LavaC #28 我之前也是这么写的,不过没有开源,我现在写 React 了,这是 React + antd 版的 https://bowencool.github.io/create-antd-modal/
    Genshin2020
        42
    Genshin2020  
       2023-12-04 08:40:35 +08:00   ❤️ 1
    一样的技术栈,但是我把 element plus 中一个 input 组件就基于业务不同,封装了几十个业务组件,select 也是,封装了几十个,然后 form 表单是基于 一个配置信息 v-for 渲染出来的,用动态组件。

    什么业务逻辑都在业务组件中封装好,校验什么的,核心就是只关注入参和出参,写好文档。

    而表单配置信息,你乐意写在一个 config.ts 里都行,或者直接写在 index.vue 同级目录下。
    lingyired
        43
    lingyired  
       2023-12-04 09:21:18 +08:00
    分情况,如果这个 dialog 的显示内容不需要特殊的布局和排版以及内容,那么可以使用命令式调用, 把 title, content ,buttons 传递进去。比如单纯的 alert, confirm 等。

    对于需要特殊布局,然后还有数据驱动的,甚至是一些逻辑的,才抽离成一个 vue 组件。
    umaker
        44
    umaker  
       2023-12-04 09:45:53 +08:00
    我认为关键在于你如何定义 dialog 的多于少。
    如果一个页面或者整个项目里要使用的 dialog 数量(按类型计算)会以数量级的方式增加,比如这次需求有 2 个弹窗,下次需求就可能加到 20 个或者更多,这种情况,是需要考虑如何从项目顶层设计才能更好地承载这样的业务模式;
    如果只是递增形式的变多,那只是业务需求而已,按照需求的描述,做好基本的代码管理就可以了。
    什么是好代码?不要让维护者看不懂你的代码就是好代码。
    crazyTanuki
        45
    crazyTanuki  
       2023-12-04 09:47:41 +08:00
    弄个 modalConfig.js 进行集中配置,自己二次封装太麻烦了
    cenbiq
        46
    cenbiq  
       2023-12-04 09:53:48 +08:00
    products
    └─ components
    ├─ product-edit.vue
    └─ product-view.vue
    ├─ product-details.vue
    └─ product-list.vue
    cenbiq
        47
    cenbiq  
       2023-12-04 09:54:26 +08:00
    ```
    SixGodHave7
        48
    SixGodHave7  
       2023-12-04 10:01:30 +08:00
    当前页面路径下开个 components 文件夹
    MENGKE
        49
    MENGKE  
       2023-12-04 10:04:16 +08:00   ❤️ 2
    ```
    xxx
    └─ index.vue
    └─ components
    └─ xxxCreateDialog.vue
    └─ xxxEditDialog.vue
    └─ ...
    ```
    zzwyh
        50
    zzwyh  
       2023-12-04 10:15:57 +08:00
    @TimPeake vscode 支持点击跳转文件的
    we21x
        51
    we21x  
       2023-12-04 10:48:49 +08:00   ❤️ 1
    组织结构
    -UserList
    - components
    - UserInfoDialog.vue
    UserList.vue

    封装
    Ref + Promise 封装成类似 ElMessageBox.confirm 的 Promise 化的 Dialog
    大概思路: https://editor.csdn.net/md/?articleId=134161250
    davin
        52
    davin  
       2023-12-04 10:55:59 +08:00
    业务类的一般都放在 business 文件夹,和 components 文件夹相同级别
    tog
        53
    tog  
       2023-12-04 10:58:27 +08:00
    你这个场景 很适合插槽,如果是我的的话,就用插槽+组件的形式, 手动狗头~
    iMiata
        54
    iMiata  
       2023-12-04 11:13:00 +08:00
    比较推荐 #49 楼的结构
    qwas
        55
    qwas  
       2023-12-04 11:59:38 +08:00   ❤️ 1
    组件也可以分为通用组件和页面组件的啊
    src
    └─ components
    └─ views
    └──── view-a
    └──── components
    JohnH
        56
    JohnH  
       2023-12-04 12:17:49 +08:00   ❤️ 1

    这是我目前的开发方式。
    1.图中可见有个路由 /company/index, /company/edit, /company/detail 。在 company 下的 components 是抽离出来的独立性比较高的组件,也被其他几个路由少量引用
    2.investment 是我目前思路的典型做法。
    - data.ts 用于本模块下提供一些共用的局部字典什么的
    - 各目录下的 vue 一定是在 router 中定义的,components 内的除外
    - components 下的永远不会被路由直接访问,是供页面引用的组件
    - 从相应页面拆分出来的组件,以页面名称为前缀
    Cheez
        57
    Cheez  
       2023-12-04 13:42:45 +08:00
    两种方法,使用 JSON Schema 。使用 useDialog() 函数
    rm0gang0rf
        58
    rm0gang0rf  
       2023-12-04 16:07:59 +08:00
    文件夹套文件夹别的啥都不方便
    giter
        59
    giter  
    OP
       2023-12-04 16:22:31 +08:00
    trokix
        60
    trokix  
       2023-12-04 16:28:14 +08:00   ❤️ 1
    感觉,你是需要函数式组件
    giter
        61
    giter  
    OP
       2023-12-04 16:33:50 +08:00
    @trokix 看起来很高级,第一次听说,谢谢,我去了解一下
    QuincyX
        62
    QuincyX  
       2023-12-04 17:34:41 +08:00   ❤️ 1
    函数式组件不能 jnject ,不方便

    我的最佳实践:
    - 每个页面都放在一个文件夹中而不是单文件组件
    - 每个文件夹下都放一个 __com__ 用来放页面模块
    - index.vue 只是用来把多个模块拼装在一起
    - 数据和逻辑用 provide/inject 共享复用
    - 也可以独立一个 js 文件存放( hooks 方式)
    QuincyX
        63
    QuincyX  
       2023-12-04 17:37:03 +08:00
    pageA
    └─ index.vue
    └─ __com__
    └─────── moduleA.vue
    └─────── moduleB.vue
    └─────── dialogA.vue
    └─────── drawerA.vue
    wuzhanggui
        64
    wuzhanggui  
       2023-12-04 18:01:34 +08:00   ❤️ 1
    AlphaTr
        65
    AlphaTr  
       2023-12-04 18:33:54 +08:00   ❤️ 2
    我们项目的做法,组件分为三级
    1. /src/components/: 通用组件,具有跨项目的共性,更多是对通用组件库的补充;例如图片裁切组件,判断依据是不调用业务 API ;
    2. /src/views/components/: 跨业务组件,多个业务都调用的基础组件,可以调用业务 API ;例如用户选择组件;
    3. /src/views/users/components/: 业务自身组件,就是楼主所说的详情对话框、编辑对话框,放在对应路由下,类似 RESTful 的思路,按照资源进行组织的,当然,路由、Views 也是 RESTful 的思路进行组织的
    as80393313
        66
    as80393313  
       2023-12-04 19:23:33 +08:00   ❤️ 1
    /src/components/dialog.vue 通用的弹窗组件,里面封装通用的弹窗功能,集成修改业务功能,新增业务功能,props 参数,包含增,修改的接口,和表单组件。不传这些就是一个普通的弹窗。dialog 通过 ref 获取表单组件实例,调用表单校验获取数据等。
    /src/views/user/__controller__/cru-dialog.vue 具体业务组件,可以看做是一个具体实现的控制层,cru 就是 crud 中 cru 没有删除功能,当然也可以拆开,具体包含了,弹窗唤起增加,修改的业务逻辑,和详情的逻辑,在这里调用通用组件的 dialog 。注意这里只负责具体业务功能,不操作表单。

    /src/views/user/components/form/cru-form.vue 具体业务组件的表单,包含表单的所有操作,表单校验,根据实际情况是否拆分详情。为上面的控制器提供表单实体。

    这样分开之后,业务具体功能,表单解耦了,弹窗也是。
    当对外调用的时候,只需要调用 cru-dialog.vue 组件即可,这个弹窗已经包含了增,改,详情。
    由于表单是独立存在,当其他页面业务可能需要详情表单,也可以直接引用表单组件。
    这种有参考 MVC 模式的想法。
    giter
        67
    giter  
    OP
       2023-12-04 20:34:02 +08:00
    @wuzhanggui 得闲会拜读一下,谢谢:)
    giter
        68
    giter  
    OP
       2023-12-04 20:35:49 +08:00
    @AlphaTr 分级分的很合理,目前看过的大多数开源 Vue 项目也是如此,谢谢:)
    sjhhjx0122
        69
    sjhhjx0122  
       2023-12-04 20:40:03 +08:00
    @QuincyX 函数组件可以 inject 只要用 inject 去写创建弹窗,别用 rander 去写就能拿到上下文了
    haodaking
        70
    haodaking  
       2023-12-05 11:23:48 +08:00

    我们项目的目录结构是这样划分的,src 根目录下有公共的 components 、composables 、api 、assets ,modules 文件夹下是每个模块,每个模块都可以有自己的 components 、composables 、路由、api 、类型、静态资源、国际化文件
    haodaking
        71
    haodaking  
       2023-12-05 11:26:20 +08:00
    @haodaking 模块注册
    Yjhenan
        72
    Yjhenan  
       2023-12-05 11:55:31 +08:00
    我喜欢写个公共组件 ButtonDialog.vue ,然后把弹窗内容单独一个组件,用的时候就用 ButtonDialog 包裹下,这样 Edit 组件方便和 Detail 组件写一块,因为没有 Dialog 这一层,额外用的时候也很方便。
    ButtonDialog 默认是按钮,动态组件也方便传其他的
    xingyuc
        73
    xingyuc  
       2023-12-05 16:19:51 +08:00
    src/module/moduleName/views, components, routes, store...
    这样每个模块的东西都在自己下面,也可以直接引用其他模块的组件
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2602 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 10:33 · PVG 18:33 · LAX 02:33 · JFK 05:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.