原文地址:How to Structure Vue Projects,作者是Alex,一位来自德国的开发人员。在 25 年的 Vue Nation
上有演讲。
快速总结
这篇文章涵盖了适合不同项目规模的各种 Vue 项目架构:
- 适用于小型项目的扁平结构
- 适用于可扩展应用的原子设计
- 适用于大型项目的模块化方法
- 适用于复杂应用的功能切片设计(Feature Sliced Design)
- 企业级解决方案的微前端方案
介绍
在开始一个 Vue 项目时,你要做出的最关键的决定之一就是如何选择架构。正确的架构可以提高团队内部的可扩展性、可维护性和协作性。这种考虑与康威定律一致:
“设计系统的组织被迫生产出复制这些组织通信结构的设计。” — 梅尔·康威
从本质上讲,你的 Vue 应用程序的架构将反映你的组织结构,影响你应该如何规划项目的布局。
无论您是构建小型应用程序还是企业级解决方案,本指南都涵盖了适合不同规模和复杂性的各种项目结构。
1. 扁平结构:非常适合小型项目
你是否正在进行一个小规模的 Vue 项目或概念验证?简单、扁平的文件夹结构可能是使事情变得简单并避免不必要的复杂性的最佳选择。
/src |-- /components | |-- BaseButton.vue | |-- BaseCard.vue | |-- PokemonList.vue | |-- PokemonCard.vue |-- /composables | |-- usePokemon.js |-- /utils | |-- validators.js |-- /layout | |-- DefaultLayout.vue | |-- AdminLayout.vue |-- /plugins | |-- translate.js |-- /views | |-- Home.vue | |-- PokemonDetail.vue |-- /router | |-- index.js |-- /store | |-- index.js |-- /assets | |-- /images | |-- /styles |-- /tests | |-- ... |-- App.vue |-- main.js
优点和缺点
✅ 优点 | ❌ 缺点 |
---|---|
易于实施 | 不可扩展 |
最少的设置 | 随着项目的增长而变得杂乱无章 |
非常适合小型团队或独立开发人员 | 缺乏明确的关注点分离 |
2. 原子设计:可扩展的组件组织
对于更大的 Vue 应用程序,原子设计(Atomic Design) 方法可能是有利的。此方法将组件组织到从最简单到最复杂的层次结构中。
原子层次结构
- 原子 Atoms:基本元素,如按钮和图标。
- 分子 Molecules:形成简单组件的原子组(例如,搜索栏)。
- 生物体 Organisms:由分子和原子组成的复杂组件(例如导航栏)。
- 布局 Layout:构建有机体而没有真实内容的页面布局。
- 页面 Pages:模板填充了真实内容以形成实际页面。
此方法可确保可伸缩性和可维护性,从而促进简单组件和复杂组件之间的平滑过渡。
/src |-- /components | |-- /atoms | | |-- AtomButton.vue | | |-- AtomIcon.vue | |-- /molecules | | |-- MoleculeSearchInput.vue | | |-- MoleculePokemonThumbnail.vue | |-- /organisms | | |-- OrganismPokemonCard.vue | | |-- OrganismHeader.vue | |-- /templates | | |-- TemplatePokemonList.vue | | |-- TemplatePokemonDetail.vue |-- /pages | |-- PageHome.vue | |-- PagePokemonDetail.vue |-- /composables | |-- usePokemon.js |-- /utils | |-- validators.js |-- /layout | |-- LayoutDefault.vue | |-- LayoutAdmin.vue |-- /plugins | |-- translate.js |-- /router | |-- index.js |-- /store | |-- index.js |-- /assets | |-- /images | |-- /styles |-- /tests | |-- ... |-- App.vue |-- main.js
优点和缺点
✅ 优点 | ❌ 缺点 |
---|---|
高度可扩展 | 可能会在管理层中引入开销 |
井然有序的组件层次结构 | 设置的初始复杂性 |
可重用组件 | 对于较小的项目来说可能有点矫枉过正 |
改善团队之间的协作 |
3. 模块化方法:基于功能的组织
随着项目的扩展,请考虑使用模块化整体架构。此结构封装了每个功能或域,增强了可维护性,并为可能向微服务演变做好准备。
/src |-- /core | |-- /components | | |-- BaseButton.vue | | |-- BaseIcon.vue | |-- /models | |-- /store | |-- /services | |-- /views | | |-- DefaultLayout.vue | | |-- AdminLayout.vue | |-- /utils | | |-- validators.js |-- /modules | |-- /pokemon | | |-- /components | | | |-- PokemonThumbnail.vue | | | |-- PokemonCard.vue | | | |-- PokemonListTemplate.vue | | | |-- PokemonDetailTemplate.vue | | |-- /models | | |-- /store | | | |-- pokemonStore.js | | |-- /services | | |-- /views | | | |-- PokemonDetailPage.vue | | |-- /tests | | | |-- pokemonTests.spec.js | |-- /search | | |-- /components | | | |-- SearchInput.vue | | |-- /models | | |-- /store | | | |-- searchStore.js | | |-- /services | | |-- /views | | |-- /tests | | | |-- searchTests.spec.js |-- /assets | |-- /images | |-- /styles |-- /scss |-- App.vue |-- main.ts |-- router.ts |-- store.ts |-- /tests | |-- ... |-- /plugins | |-- translate.js
备选方案:简化的扁平化特征结构
大型项目中的一个常见痛点是过多的文件夹嵌套,这会使导航和文件发现更加困难。下面是一个简化的扁平功能结构,它优先考虑 IDE 友好的导航并减少认知负载:
/features |-- /project | |-- project.composable.ts | |-- project.data.ts | |-- project.store.ts | |-- project.types.ts | |-- project.utils.ts | |-- project.utils.test.ts | |-- ProjectList.vue | |-- ProjectItem.vue
这种结构具有以下几个优点:
- 快速导航:使用“快速打开”(Ctrl/Cmd + P) 等 IDE 功能,您可以通过键入“project...”立即找到任何与项目相关的文件。
- 减少嵌套:所有与功能相关的文件都位于同一级别,从而消除了较深的文件夹层次结构
- 清除所有权:每个文件的用途从其后缀中立即明确
- 轻松的模式识别:一致的命名使理解每个文件的作用变得简单
- 测试共存:测试位于它们正在测试的代码旁边
4. 功能切片设计:适用于复杂的应用
功能切片设计(Feature-Sliced Design) 非常适合大型长期项目。这种方法将应用程序分解为不同的层,每个层都有特定的角色。
特征切片设计的层次
- 应用程序 App:全局设置、样式和提供程序。
- 过程 Processes:公用业务流程,如用户身份验证流。
- 页面 Pages:使用实体、功能和小组件构建的完整页面。
- 部件 Widgets:将实体和功能合并到内聚的 UI 块中。
- 特征 Features:处理增加价值的用户交互。
- 实体 Entities:表示核心业务模型。
- 共享 Shared:与特定业务逻辑无关的可重用实用程序和组件。
/src |-- /app | |-- App.vue | |-- main.js | |-- app.scss |-- /processes |-- /pages | |-- Home.vue | |-- PokemonDetailPage.vue |-- /widgets | |-- UserProfile.vue | |-- PokemonStatsWidget.vue |-- /features | |-- pokemon | | |-- CatchPokemon.vue | | |-- PokemonList.vue | |-- user | | |-- Login.vue | | |-- Register.vue |-- /entities | |-- user | | |-- userService.js | | |-- userModel.js | |-- pokemon | | |-- pokemonService.js | | |-- pokemonModel.js |-- /shared | |-- ui | | |-- BaseButton.vue | | |-- BaseInput.vue | | |-- Loader.vue | |-- lib | | |-- api.js | | |-- helpers.js |-- /assets | |-- /images | |-- /styles |-- /router | |-- index.js |-- /store | |-- index.js |-- /tests | |-- featureTests.spec.js
优点和缺点
✅ 优点 | ❌ 缺点 |
---|---|
高内聚力和清晰的分离 | 理解层的初始复杂性 |
可扩展且可维护 | 需要周密的规划 |
促进团队协作 | 需要一致地执行约定 |
了解有关特征切片设计的更多信息
访问官方 Feature-Sliced Design 文档以深入了解。
5. 微前端:企业级解决方案
微前端将微服务的概念带到了前端。不同的团队可以独立处理 Web 应用程序的各个部分,从而实现灵活的开发和部署。
关键组件
- 应用程序壳:主控制器处理基本的布局和路由,连接所有微前端。
- 分解的 UI 和模块:每个微前端都专注于应用程序的特定部分,并且可以使用不同的技术进行开发。
优点和缺点
✅ 优点 | ❌ 缺点 |
---|---|
独立部署 | 编排中的高复杂性 |
大型团队的可扩展性 | 需要强大的基础设施 |
与技术无关的方法 | 用户体验中可能存在的不一致 |
微前端最适合具有多个开发团队的大型复杂项目。这种方法可能会带来很大的复杂性,并且对于中小型应用程序通常不是必需的。
结论
选择正确的项目结构取决于项目的大小、复杂性和团队组织。您的团队或项目越复杂,您就越应该致力于促进可扩展性和可维护性的结构。
您的项目架构应与您的组织一起成长,为未来的发展提供坚实的基础。
对比
方法 | 描述 | ✅优点 | ❌缺点 |
---|---|---|---|
扁平结构 | 适用于小型项目的简单结构 | 易于实施 | 不可扩展,可能会变得杂乱无章 |
原子设计 | 基于组件的分层结构 | 可扩展、有序、可重用的组件 | 管理层的开销,初始复杂性 |
模块化方法 | 基于特征的模块化结构 | 可扩展的封装功能 | 潜在的重复,需要纪律 |
特征切片设计 | 适用于大型项目的功能层和切片 | 高内聚力,分离清晰 | 初始复杂性,需要全面规划 |
微前端 | 前端组件的独立部署 | 独立部署,可扩展 | 高度复杂,需要团队之间的协调 |
一般规则和最佳实践
在结束之前,让我们强调一些可以应用于每种结构的一般规则。这些准则对于保持代码库的一致性和可读性非常重要。
基础组件名称
为您的 UI 组件使用前缀,以将其与其他类型组件区分开来。
Bad
components/ |-- MyButton.vue |-- VueTable.vue |-- Icon.vue
Good
components/ |-- BaseButton.vue |-- BaseTable.vue |-- BaseIcon.vue
组件名称和功能紧密耦合
通过和命名紧密耦合的组件,将它们分组在一起。
Bad
components/ |-- TodoList.vue |-- TodoItem.vue |-- TodoButton.vue
Good
components/ |-- TodoList.vue |-- TodoListItem.vue |-- TodoListItemButton.vue
组件名称中的单词顺序
组件名称应以最高级别的单词开头,并以描述性修饰符结尾。
Bad
components/ |-- ClearSearchButton.vue |-- ExcludeFromSearchInput.vue |-- LaunchOnStartupCheckbox.vue
Good
components/ |-- SearchButtonClear.vue |-- SearchInputExclude.vue |-- SettingsCheckboxLaunchOnStartup.vue
组织测试
决定是将测试保存在单独的文件夹中,还是与组件一起保存。这两种方法都是有效的,但一致性是关键。
方法 1:单独的测试文件夹
/vue-project |-- src | |-- components | | |-- MyComponent.vue | |-- views | | |-- HomeView.vue |-- tests | |-- components | | |-- MyComponent.spec.js | |-- views | | |-- HomeView.spec.js
方法 2:内联测试文件
/vue-project |-- src | |-- components | | |-- MyComponent.vue | | |-- MyComponent.spec.js | |-- views | | |-- HomeView.vue | | |-- HomeView.spec.js