【翻译】现代前端应用程序架构

翻译自：Architectures of modern Front-end applications

在开发过程中，业务需求可能各不相同且经常变化，因此创建一个灵活、可扩展和可维护的架构非常重要。同样关键的是，团队成员和客户等每个人都要清楚地了解项目。为了避免大量文件、频繁开会和不断改进，我们采用了下面的架构方法。本文章将探讨流行架构的优势，并帮助您选择最佳解决方案来满足您的独特需求。

很多人认为下面列出的架构只是 "文件夹结构"，这只是一种说法。事实上，如果深入研究，它们有几个重要方面：

模块合作：应用程序中不同模块 / 组件之间的高效通信和互动（使用基于组件的架构、可重复使用的代码）。

改进项目导航：以易于导航和维护的方式构建项目（清晰的文件夹层次结构、命名约定、关注点分离）。

业务逻辑与用户界面组件分离：将业务逻辑（数据获取、状态管理）与用户界面组件分离，以提高可维护性和可重用性（例如，使用 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 / 等等。

使用 "模块化架构" 的实例：

经典 React 应用程序中的模块化

Vue3 应用程序中的模块化

React Native 应用程序中的模块化

使用 "FSD 架构" 的实例：

经典 React 应用程序中的 FSD

Vue3 应用程序中的 FSD

Next.js 应用程序中的 FSD

React Native 应用程序中的 FSD

结论

在本文中，我们探讨了经典架构、模块化架构和 FSD 架构之间的区别，并讨论了它们的用途。