在开发过程中,业务需求可能各不相同且经常变化,因此创建一个灵活、可扩展和可维护的架构非常重要。同样关键的是,团队成员和客户等每个人都要清楚地了解项目。为了避免大量文件、频繁开会和不断改进,我们采用了下面的架构方法。本文章将探讨流行架构的优势,并帮助您选择最佳解决方案来满足您的独特需求。
很多人认为下面列出的架构只是 "文件夹结构",这只是一种说法。事实上,如果深入研究,它们有几个重要方面:
- 模块合作:应用程序中不同模块 / 组件之间的高效通信和互动(使用基于组件的架构、可重复使用的代码)。
- 改进项目导航:以易于导航和维护的方式构建项目(清晰的文件夹层次结构、命名约定、关注点分离)。
- 业务逻辑与用户界面组件分离:将业务逻辑(数据获取、状态管理)与用户界面组件分离,以提高可维护性和可重用性(例如,使用 Redux、Context API 等服务或存储)。
- DRY: Don’t repeat yourself 不要重复
- DAC: Divide and conquer 分而治之
请看下图 -- 我们的重点是右下角的区域,这里是这些原则最有效的结合点:
理想结果(示例)
我们希望借助架构来实现这些方面。现在,让我们对每一种架构进行详细分析,并根据具体情况选择最适合的架构。
经典架构(无架构)- Classic architecture (without architecture)#
经典架构是许多人已经在使用的一种方法。我们通常专注于基本概念,将项目划分为 “页面”、“组件”、“助手” 等。然而,问题在于随着应用程序的增长,结构开始瓦解,找到正确的组件或其业务逻辑变得更加困难。让我们举例说明:
组件过度使用(示例)
在这个例子中,我们有 3 个页面,显然没有被过度使用。但是,它们可能包含下面显示的所有组件。如果我们观察一下这些组件,就会发现真正的 "混乱"🤯:每个组件都会主动使用其他组件,从而在它们之间产生依赖关系。这使得它们难以扩展和重用。
下面是另一个使用 Redux 状态管理器的示例:
分散的应用程序逻辑 - Redux 状态管理器(示例)
在我们的设置中,每个组件都由指定的 "还原器" 管理,该还原器负责处理其特定的逻辑。然而,有些组件逻辑被错误地放在了不正确的还原器中。出现这种情况的原因可能是开发人员没有注意到现有的文件,或者由于当时需要的逻辑数量有限而没有考虑创建一个新文件。因此,一些组件的逻辑现在分散在整个项目中,使其不够清晰,也更难维护。
下图展示了一个缺失的架构:
破坏性解耦示意图(示例)
这种方法(或者更正确地说,缺乏架构)往往会造成难以跟踪依赖关系的混乱环境,从而导致混乱,难以为项目提供支持。不过,这种方法在一些特殊情况下可能还是有用的,例如:
- 小型团队(1-2 名开发人员)
- MVP 项目
- 不是长期支助项目
- 学习项目或模板
模块化结构 - Modular architecture#
模块化架构是一种将应用程序划分为若干层( pages 、 modules 、 components 、 UI 等)的方法,在这些层中,已经有独立的模块,它们有自己的逻辑和责任范围。
模块化架构 -- 层结构(示例)
在这个例子中,可以看到应用层的排列方向是一致的: pages → modules → components → ui (如果从另一侧看,则相反)。这意味着层数越高(例如 pages ),可以使用的下层层数就越少 -- 组件不能使用模块,但可以使用用户界面层的所有内容,而模块可以使用组件,但不能使用页面。而页面已经只能使用模块了。
模块化架构 -- 模块和公共应用程序接口(示例)
如前所述,每个模块都有自己的职责范围。同样重要的是,每个模块都应该有自己的公共 API( index.ts 文件),它封装了模块的所有内部逻辑,只提供外部需要的部分。(这与 OOP 原理非常相似:当一个类有很多私有方法时,这些方法不能从外部访问,但可以在类本身内部使用)说到 pages 文件,其实很简单:理想情况下,它应该只是模块和组件的封装,应用程序的所有业务逻辑都应该放在模块和组件的层面上。
(⚠️) 重要:一个模块不应使用另一个模块,一个组件不应包含复杂的逻辑。如果仍然需要逻辑,则应尽可能简单和易于维护,否则 -- 它就是一个模块!
请看下图:
近似理想图(示例)
但是我们仍然有 components/ 和 ui/ 这样的全局目录,它们可能会被过度使用。在某些情况下,逻辑可能会增长,不再总是很清楚什么是组件,什么是模块。此外,我们还经常发现,随着应用程序的增长,开发人员开始在其他模块中使用模块,这就破坏了该架构的原则,并造成不必要的依赖。尽管如此,我们的架构提供了以下功能:
- 单线程
- 在不同层级重复使用组件的能力
- 近乎完美的层次感
- 封装
功能分片设计(FSD)架构 - Feature Sliced Design#
概述 - https://feature-sliced.design/
功能切片设计(FSD)架构 -- 与模块化架构有很多相似之处,但也避免了我们上面讨论过的情况。这种方法是按功能区域(特性)而不是按层来构建项目。这种组织方式有助于避免全局目录增长(如模块化架构中的 components 、 UI ),并在组件、模块和层之间提供了明确的职责分工。
FSD - 图层示例
架构的构建方式是,顶层 pages 整合并组织所有子模块和组件的工作,而每一层则提供更详细、更具体的功能和元素。是的,我们在这里遵循同样的规则 -- 层级越高(例如 pages ),可以使用的下层层级就越少:
- 页面 - 顶层包含应用程序中显示的 pages 。
- 进程 - 在此架构中已被弃用,因此我们可以跳过它。
- 功能 / 小工具 - pages 下面是主要的 features 块,这些块构建了页面的核心功能,使其具有可管理性和独立性。
- 实体(Entities)-- 功能模块下面的 entities 是由 "共享" 层中可用的较简单用户界面组件编译而成的。
- 共享 - 底层包含通用的用户界面组件,可用于应用程序的不同部分。
(⚠️) 重要:与模块化架构一样,各层之间不要过度使用。
概述(片 / 段)- https://feature-sliced.design/
FSD 体系结构以模块化元素为特色,称为 "片" 和 "段"。"切片" 指的是每一层中的模块,每个模块代表一个不同的业务实体。同时,"段" 包含各种结构组件,如 api/
、 components/
、 config/
、 constants/
等,将架构组织成更清晰、更易于管理的部分。
最后,我们达到了目标结果:
理想 - 图表(示例)
将这种方法集成到项目中既不容易也不快速。它至少需要最低限度的架构知识,而且你必须牢记这可能需要时间。但是,掌握 FSD 的使用方法可以:
- 结构清晰
- 层次分明
- 灵活组件
- 独立模块
- 平衡重用性
顺便说一句,这种架构提倡在文件命名中使用 "kebab case"👀。
product-description.vue
/shopping-cart.tsx
/get-base-url.ts
/ 等等。
使用 "模块化架构" 的实例:
使用 "FSD 架构" 的实例:
结论#
在本文中,我们探讨了经典架构、模块化架构和 FSD 架构之间的区别,并讨论了它们的用途。