告别面条代码!大型Vue3项目的目录架构与模块化分层设计
开篇引言你的 Vue3 项目是不是正在变成另一个 jQuery 时代的噩梦很多团队兴冲冲地升级到了 Vue3用上了script setup和 TypeScript以为这就算踏入了现代化前端开发的门槛。然而半年后当你打开项目根目录面对的是这样的景象components文件夹里躺着 50 个.vue文件命名从UserList.vue到UserListNew.vue再到UserListFinal.vue没人敢删一个新来的同事想改登录页的一个按钮样式却不得不先读懂src/api/下 3000 行的request.ts和src/utils/里十几个毫不相干的工具函数产品经理提了一个“在订单详情里加一个物流追踪地图”的需求你发现地图组件和订单组件互相 import形成了一个死循环依赖……这不是 Vue3 的锅这是架构的锅。如果你只是把 Vue3 当成“更好的 Vue2”来用那你的项目终将不可避免地滑向“面条代码”的深渊。在大型项目中框架只是工具架构才是灵魂。今天我们就来聊聊如何用领域驱动设计DDD思想和垂直切片架构彻底重构你的 Vue3 项目目录让组件和逻辑实现真正的物理级解耦。一、 为什么传统的“技术角色”分层在大型项目中会失效我们先来看看大多数 Vue 项目的标准目录结构也就是 Vue CLI 或 Vite 脚手架默认推荐的结构src/ ├── api/ # 所有接口请求 ├── assets/ # 静态资源 ├── components/ # 全局公共组件 ├── router/ # 路由 ├── store/ # 状态管理 ├── utils/ # 工具函数 └── views/ # 页面级组件这种按“技术角色”划分的方式在小项目中非常直观。​ 但问题在于它没有反映业务的边界。想象一下你的项目有“用户中心”、“商品交易”、“营销活动”三大业务板块。当你要修改“用户中心的收货地址”时你需要同时改动views/UserCenter/Address.vueapi/userApi.ts里面可能还混杂着商品和用户的所有接口store/modules/user.tscomponents/AddressForm.vue万一这个组件也被交易模块用了呢这就是典型的“横切耦合”。​ 一个业务功能的变更需要跨越 N 个技术目录去修改代码开发效率极低且极易引发回归 Bug。二、 破局之道引入领域驱动设计DDD与垂直切片在后端架构中领域驱动设计Domain-Driven Design, DDD​ 已经被证明是解决复杂业务耦合的银弹。它的核心思想是按照业务领域Domain而非技术层级来组织代码。映射到前端我们要做的就是把“横向分层”改为“垂直切片Vertical Slices”。 核心概念什么是垂直切片一个业务功能所需的所有代码UI、逻辑、数据、样式应该尽可能地待在同一个地方。就像一把刀切下去这一片里包含了这个功能所需的全部要素而不是把所有 UI 放一堆、所有逻辑放一堆。三、 实战重构你的 Vue3 项目目录下面是一套经过多个大型 ToB 和 ToC 项目验证的 Vue3 TypeScript 企业级目录结构src/ ├── app/ # 应用级配置只初始化一次的东西 │ ├── main.ts # 入口文件 │ ├── App.vue # 根组件 │ ├── router/ # 路由定义仅路由表不含业务逻辑 │ ├── store/ # 全局状态仅放真正全局的如用户信息、主题配置 │ └── plugins/ # 第三方插件初始化如 axios 实例、监控 SDK │ ├── entities/ # 业务实体跨领域共享的数据模型 │ ├── User/ │ │ ├── types.ts # User 的类型定义 │ │ ├── constants.ts # User 相关常量 │ │ └── utils.ts # User 相关的纯函数工具如格式化用户名 │ └── Product/ │ ├── types.ts │ └── constants.ts │ ├── features/ # 业务特征模块核心按领域垂直切片 │ ├── auth/ # 认证模块 │ │ ├── components/ # 仅属于 auth 的组件LoginForm, SocialAuth │ │ ├── composables/ # auth 相关的组合式函数useLogin, useRegister │ │ ├── api.ts # auth 相关的接口 │ │ ├── store.ts # auth 的局部状态如果用 Pinia 分模块 │ │ ├── types.ts # auth 特有的类型 │ │ └── index.ts # 统一导出barrel file │ │ │ ├── order/ # 订单模块 │ │ ├── components/ # OrderList, OrderDetail, OrderStatusBadge │ │ ├── composables/ # useOrderList, useOrderCancel │ │ ├── api.ts │ │ ├── store.ts │ │ └── index.ts │ │ │ └── dashboard/ # 仪表盘模块 │ └── ... │ ├── shared/ # 跨领域共享资源被多个 features 引用 │ ├── components/ # 真正的通用基础组件Button, Modal, Table │ │ ├── BaseButton/ │ │ │ ├── BaseButton.vue │ │ │ └── index.ts │ │ └── index.ts # 统一导出方便 auto-import │ ├── composables/ # 通用组合式函数useDebounce, useLocalStorage │ ├── utils/ # 通用工具formatDate, deepClone │ ├── hooks/ # 通用 hooks如 useTheme, useBreakpoint │ └── styles/ # 全局样式、变量、mixins │ └── widgets/ # 业务挂件可组合的页面区块介于组件和页面之间 ├── HeaderWidget/ ├── SidebarWidget/ └── UserProfileCard/四、 逐层拆解每一层该放什么不该放什么1️⃣app/—— 应用的骨架不是业务的垃圾桶只放应用启动配置路由实例、Pinia 实例、全局错误处理全局插件注册根组件不放❌ 任何具体业务逻辑❌ 具体 API 调用❌ 具体业务组件判断标准如果把features/和shared/全部删掉只靠app/能不能跑起来一个空白页面如果能说明你的app/层是干净的。2️⃣entities/—— 业务实体的契约层这里存放的是跨领域共享的数据模型和类型定义是整个项目的词汇表。// src/entities/User/types.ts export interface User { id: string name: string email: string role: UserRole avatar?: string } export type UserRole admin | editor | viewer // src/entities/User/utils.ts import type { User } from ./types export const formatUserName (user: User): string { return user.name || user.email.split()[0] } export const isAdmin (user: User): boolean { return user.role admin }关键点entities/层绝对不能引用​features/或shared/中的任何东西单向依赖原则它是最底层的基础像地基一样稳定如果 A 模块和 B 模块都需要用到同一个数据类型它就应该住在entities/里3️⃣features/—— 你的业务逻辑心脏这是整个架构中最重要的一层。每个 feature 就是一个垂直切片。features/auth/ ├── components/ │ ├── LoginForm.vue # 登录表单只在这个模块内用 │ └── SocialAuthBtn.vue # 社交登录按钮 ├── composables/ │ ├── useLogin.ts # 登录逻辑可复用、可测试 │ └── useAuthGuard.ts # 鉴权守卫 ├── api.ts # 登录/注册/登出的接口 ├── store.ts # auth 的 Pinia store ├── types.ts # auth 特有的类型如 LoginParams, TokenInfo └── index.ts # 统一导出useLogin.ts示例// features/auth/composables/useLogin.ts import { ref } from vue import { useRouter } from vue-router import { login as loginApi } from ../api import type { LoginParams } from ../types export function useLogin() { const router useRouter() const loading ref(false) const error refstring | null(null) const login async (params: LoginParams) { loading.value true error.value null try { const { token } await loginApi(params) localStorage.setItem(token, token) router.push(/dashboard) } catch (e: any) { error.value e.message || 登录失败 } finally { loading.value false } } return { login, loading, error } }然后在组件中!-- features/auth/components/LoginForm.vue -- script setup langts import { reactive } from vue import { useLogin } from ../composables/useLogin const { login, loading, error } useLogin() const form reactive({ username: , password: }) const handleSubmit () { login(form) } /script template form submit.preventhandleSubmit input v-modelform.username placeholder用户名 / input v-modelform.password typepassword placeholder密码 / button typesubmit :disabledloading {{ loading ? 登录中... : 登录 }} /button p v-iferror classerror{{ error }}/p /form /template看看这个组件有多干净​ 它只关心渲染表单和调用useLogin所有的业务逻辑、接口请求、路由跳转都藏在 composable 里。这就是极致解耦。4️⃣shared/—— 真正通用的东西才配待在这里严格准入原则放进shared/的东西必须满足——至少被 3 个不同的 feature 引用否则就应该放在对应的 feature 内部。shared/components/ ├── BaseButton/ # ✅ 被 10 个 feature 用 → 放这里 ├── BaseModal/ # ✅ 同上 └── OrderTable/ # ❌ 只有 order 模块用 → 应该在 features/order/components/ shared/composables/ ├── useDebounce.ts # ✅ 通用防抖逻辑 ├── useEventListener.ts # ✅ 通用事件监听 └── useOrderPolling.ts # ❌ 订单专用 → 应该在 features/order/composables/常见误区把shared/当成不知道放哪就扔这的垃圾桶。这会让你以为自己在做复用实际上是在制造隐式耦合。5️⃣widgets/—— 页面级的乐高积木Widgets 是由多个组件组合而成、具有独立业务含义的页面区块。它比组件大比页面小。!-- widgets/UserProfileCard/UserProfileCard.vue -- script setup langts import { useUserStore } from /stores/user import BaseAvatar from /shared/components/BaseAvatar/BaseAvatar.vue import BaseButton from /shared/components/BaseButton/BaseButton.vue const userStore useUserStore() /script template div classuser-profile-card BaseAvatar :srcuserStore.avatar :altuserStore.name / div classinfo h3{{ userStore.name }}/h3 p{{ userStore.bio }}/p /div BaseButton clickuserStore.editProfile编辑资料/BaseButton /div /template这个 Widget 可以在 Dashboard 页、设置页、个人主页等多个地方复用但它内部已经组装好了完整的 UI 和交互逻辑。五、 依赖流向一张图看懂各层关系┌─────────────────────────────────────────────┐ │ app/ │ │ 路由、全局配置、入口 │ └──────────────────┬──────────────────────────┘ │ 引用 ▼ ┌─────────────────────────────────────────────┐ │ features/ │ │ 业务模块垂直切片核心逻辑 │ │ │ │ auth ◄──────► order ◄──────► dashboard │ │ │ │ │ │ │ └──────────────┼──────────────┘ │ │ │ 引用 │ └───────────────────┼─────────────────────────┘ ▼ ┌───────────────────┐ │ entities/ │◄──────┐ │ 数据模型、类型 │ │ └─────────┬─────────┘ │ │ 被引用 │ ▼ │ ┌───────────────────┐ │ │ shared/ │───────┘ │ 通用组件、工具 │ 引用 entities └───────────────────┘铁律依赖只能从上往下绝不反向。层级可以引用不可以引用app/features, shared, entities无限制features/shared, entitiesapp, 其他 features尽量避免entities/什么都不引用纯类型纯函数所有其他层shared/entities类型引用features, app如果 feature A 必须要引用 feature B 的代码怎么办答案提取到entities/或shared/。比如订单模块需要用户信息不应该直接 import 用户模块的组件而是 importentities/User/types.ts里定义的User类型或者通过 Pinia store 来获取用户数据store 放在app/store/或shared/store/。六、 配合 Vite 的路径别名让导入更优雅在vite.config.ts中配置import { defineConfig } from vite import path from path export default defineConfig({ resolve: { alias: { : path.resolve(__dirname, src), app: path.resolve(__dirname, src/app), features: path.resolve(__dirname, src/features), entities: path.resolve(__dirname, src/entities), shared: path.resolve(__dirname, src/shared), widgets: path.resolve(__dirname, src/widgets), } } })然后你就可以这样写// ❌ 之前相对路径地狱 import { useLogin } from ../../../features/auth/composables/useLogin // ✅ 现在清晰明了 import { useLogin } from features/auth/composables/useLogin import type { User } from entities/User/types import BaseButton from shared/components/BaseButton再配合tsconfig.json的paths配置你还能获得完美的 TypeScript 类型跳转和自动补全支持。七、 避坑指南迁移过程中的 3 个致命误区❌ 误区 1一次性全量重构别这么做。​ 在一个 50 页面的老项目中试图用一个周末把所有代码搬到新目录是不现实的也是极其危险的。✅正确做法增量迁移新功能先行。新建功能直接按新架构来每次修改老功能时顺手把它迁移到新结构用 2-3 个月的时间平滑过渡❌ 误区 2过度拆分有些团队学了 DDD 之后把每个组件拆成index.vuestyles.tslogic.tstypes.ts一个简单的按钮组件搞出 5 个文件。✅正确做法按复杂度决定拆分粒度。简单组件一个.vue文件搞定中等组件.vuecomposables.ts复杂模块完整目录结构记住架构是为了服务开发效率不是为了炫技。❌ 误区 3忽视 Barrel File统一导出没有index.ts的统一导出你的 import 语句会变成这样import { LoginForm } from features/auth/components/LoginForm/LoginForm.vue✅加上index.ts// features/auth/components/index.ts export { default as LoginForm } from ./LoginForm/LoginForm.vue export { default as SocialAuthBtn } from ./SocialAuthBtn/SocialAuthBtn.vue然后import { LoginForm, SocialAuthBtn } from features/auth/components清爽多了而且以后组件内部目录调整只要index.ts的导出不便所有消费方都不受影响。这就是接口隔离的魅力。八、 总结好的架构是长出来的不是设计出来的这篇文章我们从为什么传统分层会失效出发引入了 DDD 和垂直切片的思想给出了一套可直接落地的 Vue3 企业级目录结构并拆解了每一层的职责边界和依赖规则。回顾一下核心要点按业务领域垂直切片而非按技术角色横向分层features/ 是心脏entities/ 是地基shared/ 是工具箱依赖只能从上往下绝不反向增量迁移拒绝一刀切重构架构服务于效率不为炫技但这只是第一步。目录结构只是物理层面的解耦真正的灵魂解耦在于——如何把业务逻辑从组件中连根拔起。在下一篇文章中我们将深入探讨组合式函数Composables的艺术教你如何写出高内聚、低耦合、可测试的业务逻辑让组件变成纯粹的视图壳。

相关新闻

Ubuntu 20.04/22.04 桌面版安装后 5 项必做配置:从换源到Root密码

Ubuntu 20.04/22.04 桌面版安装后 5 项必做配置:从换源到Root密码

Ubuntu 20.04/22.04 桌面版安装后的5项关键配置指南刚完成Ubuntu桌面版的安装?恭喜你迈出了Linux世界的第一步!但先别急着庆祝——系统初始状态的配置往往决定了后续使用的流畅度。本文将带你完成5项直接影响开发效率的核心配置,从软件源替换…

2026/7/6 22:27:34阅读更多 →
Windows 11 文件备注管理:原生属性、快捷方式与第三方工具 3 方案对比

Windows 11 文件备注管理:原生属性、快捷方式与第三方工具 3 方案对比

Windows 11 文件备注管理:原生方案、快捷方式与第三方工具深度评测在数字信息爆炸的今天,高效的文件管理已成为现代职场人士的核心竞争力之一。想象一下这样的场景:你的项目文件夹中混杂着数百个命名混乱的文档,或是需要从多年积累…

2026/7/6 22:27:34阅读更多 →
Frida Native层Hook实战:寻址策略、参数解析与性能调优

Frida Native层Hook实战:寻址策略、参数解析与性能调优

1. 项目概述:从JVM到Native的Hook深水区搞Android逆向和安全研究的朋友,对Frida肯定不陌生。前两篇我们聊了Java层的Hook,从基础API拦截到复杂对象操作,算是把“上层建筑”摸了个大概。但真正硬核的、能触及应用核心逻辑的战场&am…

2026/7/6 22:27:34阅读更多 →
Unity Burst Compiler 1.8 性能剖析:3 种数学计算场景对比,加速比最高达 120 倍

Unity Burst Compiler 1.8 性能剖析:3 种数学计算场景对比,加速比最高达 120 倍

Unity Burst Compiler 1.8 性能剖析:3 种数学计算场景对比当我们需要在Unity中处理大规模数学运算时,Burst Compiler往往能带来惊人的性能提升。最新发布的1.8版本在数学计算优化方面有了显著改进,特别是在矩阵运算、噪声生成和物理模拟这三个…

2026/7/6 23:42:39阅读更多 →
用友NC系统安全深度剖析:SQL注入、XXE与控制台绕过的组合攻击链与加固实战

用友NC系统安全深度剖析:SQL注入、XXE与控制台绕过的组合攻击链与加固实战

1. 项目概述:一次对用友NC系统典型漏洞的深度剖析最近在内部安全审计和外部漏洞情报中,用友NC系列产品的几个老生常谈但又极具威胁的漏洞组合——SQL注入、XXE以及控制台绕过——再次频繁出现。这不仅仅是几个孤立的技术点,它们共同勾勒出了一…

2026/7/6 23:42:39阅读更多 →
动态污点分析实战:从原理到漏洞挖掘的追踪技术

动态污点分析实战:从原理到漏洞挖掘的追踪技术

1. 项目概述:从“黑盒”到“染色”的追踪艺术在漏洞挖掘这个行当里待久了,你一定会遇到一种让人头疼的情况:面对一个复杂的、交互逻辑繁多的应用程序,你明知道某个输入点可能有问题,但数据流进去之后,就像石…

2026/7/6 23:42:39阅读更多 →
Transformer 架构演进:从 BERT 到 GPT-4 的 3 种核心变体与适用场景解析

Transformer 架构演进:从 BERT 到 GPT-4 的 3 种核心变体与适用场景解析

Transformer 架构演进:从 BERT 到 GPT-4 的 3 种核心变体与适用场景解析 2017年,Google的研究团队在论文《Attention is All You Need》中首次提出了Transformer架构,这一创新彻底改变了自然语言处理领域的格局。与传统循环神经网络&#xff…

2026/7/6 23:42:39阅读更多 →
Python爬虫实战:解决AES解密中的UTF-8解码错误

Python爬虫实战:解决AES解密中的UTF-8解码错误

1. 项目概述:当爬虫遇上AES加密做爬虫的朋友,估计都遇到过这个让人头疼的场景:目标网站的数据,不再是明晃晃的HTML标签,而是变成了一串串看不懂的、像乱码一样的加密字符串。你兴冲冲地用requests把数据抓下来&#xf…

2026/7/6 23:42:39阅读更多 →
EM3080-W与PIC18F56K42的条形码识别系统设计

EM3080-W与PIC18F56K42的条形码识别系统设计

1. EM3080-W与PIC18F56K42的硬件协同设计在条形码识别系统中,EM3080-W作为专用解码芯片与PIC18F56K42微控制器的组合,展现出了独特的硬件协同优势。EM3080-W是专为条形码识别优化的CMOS图像传感器,其内置的硬件解码引擎可以显著降低主控芯片的…

2026/7/6 23:37:39阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/6 4:26:20阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/6 2:48:33阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/6 0:10:35阅读更多 →
Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南

Seraphine:基于LCU API的英雄联盟智能游戏助手技术解析与应用指南 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine 技术架构先行:官方接口的合规应用 你是否曾在BP阶段手忙脚乱&#x…

2026/7/6 0:03:39阅读更多 →
多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理

多协议远程连接管理工具mRemoteNG:告别混乱,统一你的远程桌面管理 【免费下载链接】mRemoteNG mRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager. 项目地址: https://gitcode.com/gh_m…

2026/7/6 0:03:39阅读更多 →
COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南

COUNT(DISTINCT) 与 GROUP BY 去重统计:5 亿数据量下的性能实测与选型指南在数据分析和处理领域,去重统计是最基础也是最频繁使用的操作之一。当数据量达到亿级规模时,不同的去重统计方法在性能上可能产生天壤之别。本文将基于 5 亿行数据的实…

2026/7/6 0:03:39阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/6 4:45:01阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/6 4:45:01阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/6 4:45:03阅读更多 →