如何创建可扩展且可维护的前端架构

2025-06-04

如何创建可扩展且可维护的前端架构

现代前端框架和库使创建可重用的 UI 组件变得容易。这是朝着创建可维护前端应用程序迈出的良好一步。然而,多年来,在众多项目中,我发现仅仅创建可重用的组件往往是不够的。随着需求变更或新需求的出现,我的项目变得难以维护。查找正确的文件或在众多文件中调试某些内容所花费的时间越来越长。

改变是必然的。我可以提升我的搜索技能,或者更熟练地使用 Visual Studio Code。但是,我通常不是唯一一个在前端工作的人。所以,我们需要重新设置前端项目。我们需要让它们易于维护和扩展。这意味着我们可以在现有功能中应用更改,同时更快地添加新功能。

高级架构

在后端开发中,我们有许多可遵循的架构模式。目前使用的两个概念是领域驱动开发 (DDD)关注点分离 (SoC)。这两个概念为前端开发增添了巨大的价值。在 DDD 中,你会尝试将相似的功能分组,并尽可能地将它们与其他组(例如模块)解耦。而在 SoC 中,我们会分离逻辑、视图和数据模型(例如使用 MVC 或 MVVM 设计模式)。

我们期望现代前端应用程序能够承担越来越多的繁重工作。随着复杂性的增加,错误也随之增多。由于用户与前端交互,我们需要一个可靠的架构,既易于维护又可扩展。我目前偏爱的架构是模块化和领域驱动的。请注意,我的愿景可能会改变,但这是我目前偏爱的方法。

高级可扩展前端架构

当用户与我们的应用程序交互时,应用路由会将用户引导至正确的模块。每个模块都完整地包含在内。但是,由于用户期望使用一个应用程序,而不是几个小应用程序,因此会存在一些耦合。这种耦合存在于特定的功能或业务逻辑上。我们可以在模块之间共享多个功能。您可以将这些逻辑放入应用层。这意味着每个模块都可以选择与应用层交互。一个很好的例子是需要通过客户端 API 连接到我们的后端或 API 网关的设置。

查看项目结构时,我们可以遵循如下所示的思路。应用层的所有代码都位于该app目录中。所有模块在该目录中都有一个目录modules。不依赖于业务逻辑的可重用 UI 组件(例如表格)位于该components目录中。

app/
assets/
components/
lib/
modules/
styles/
Enter fullscreen mode Exit fullscreen mode

其余目录包含我们的静态assets函数(例如图像)或辅助函数lib。辅助函数可以非常简单。它们可以将某些内容转换为特定格式,或者帮助处理对象。但该lib目录中可能包含更复杂的代码。处理模式或图(例如,用于检查有向图中循环的算法)也不例外。

许多人使用类似styled-componentsCSS-in-JS或styled-components 的东西,但我更喜欢纯 CSS。为什么?因为我们可以使用 CSS 和 HTML 来解决许多 UI 问题,而无需 JavaScript。对我来说,应用 SoC 的概念后,这变得更容易。此外,将 CSS 集中维护可以减少重复代码,从而提高可维护性。这需要坚实的 CSS 架构。虽然我会在另一篇博文中讨论这个问题,但我的 CSS 架构基于Harry Roberts 的 ITCSS

填写申请详情

有了总体架构和项目结构,我们已经有了一个很好的开端。但是,为了实现这个前端架构,我们需要更多有关各个方面的细节。首先,让我们看一个更详细的架构图,如下所示。在此图中,我放大了应用层,也放大了一个模块。应用层是我们前端应用程序的核心,所以我们首先讨论它。

模块的详细架构

应用层由两部分组成:存储和客户端 API。存储是我们的全局应用程序状态。此状态保存可供不同模块同时访问的数据。即使屏幕上不需要这些数据,它也会保留在存储中。如您所见,每个发送到存储的更新请求都会经过一系列逻辑链。这就是我们所说的中间件。例如,Redux就使用过这种模式。一个简单的中间件示例是记录存储传入的请求。

有时,传入 store 的请求需要使用来自外部服务的数据进行增强。使用 Redux,我们使用一个外部服务Promise来处理此类调用。这可以是我们的后端服务,也可以是公开的第三方 API。fetch对于单一用途的 REST 调用,仅使用浏览器 API 就足够了。当您想在各种调用中使用相同的 API 时,创建 API 客户端定义可能是一个好主意。

基本的 API 客户端可以处理外部请求、响应和错误。您甚至可以让它提供请求状态信息(例如,加载)。更复杂的 API 客户端可以处理更多功能。有些 API 通过 Web 套接字连接,甚至连接到 GraphQL API。在这种情况下,您拥有更多配置选项,如下所示。

API 客户端的剖析

在更复杂的 API 客户端中,我们可以通过中间件修改所有传出的请求(例如添加身份验证标头)。响应可以使用后续软件进行修改(例如更改数据结构)。修改响应后,我们会将其存储在客户端的缓存中,这就像我们的应用商店一样。区别在于:缓存只处理传入的 API 数据,而我们可以将任何数据放入您的应用商店。

许多前端应用程序都会有一个专用的后端服务与之通信。它可能是位于包含众多微服务的Kubernetes集群之上的 API 网关,也可能是一个单一的单体式后端。但有时我们需要连接到不同的外部服务。基于此架构,我们可以创建多个 API 客户端。每个 API 客户端都可以包含缓存、中间件和后续组件。应用程序的不同部分应该能够与每个 API 客户端进行交互。

app目录对应的项目结构可能类似于:

app/
  api/
config/
  store/
pubsub/
  schemas/
  index.js
Enter fullscreen mode Exit fullscreen mode

app现在您应该对其中的两个目录很熟悉了:apistore。它们包含与已描述用例相关的所有内容。config包含整个应用程序中使用的静态定义和配置(例如常量)。schema描述了 JavaScript 对象的特定数据结构。使用 TypeScript 或 JavaScript 时都可以使用它。应用程序的所有通用模式都存储在该schemas目录中。

pubsub是一个很好的示例,它能够扩展我们前端的基本架构。我们可以使用它进行pubsub模块通信或管理计划任务。由于它对应用程序的核心至关重要,因此它位于app目录中。最后,我们有index.js文件。在这个文件中,我们可以从app目录中添加所有函数和常量。这意味着该文件中的函数是我们应用程序逻辑的入口点。

模块架构

描述完应用层后,我们只剩下模块了。详细的架构图已经展示了模块的内部结构。当应用路由指向特定模块时,该模块会决定路由的继续方式。模块路由决定显示哪个页面。一个页面包含许多 UI 组件,这些组件就是用户在屏幕上看到的内容。

在这种情况下,页面与 UI 组件并无区别。它是一个大型 UI 组件。但是,其他模块可以与组件(和操作)交互,但不能与页面交互。不同模块的页面之间交互的唯一方式是使用嵌套路由。这意味着您将模块路由放在来自不同模块的页面中。

组件通过操作与应用层交互。这些操作可以采用不同的格式。它们可以是普通的 JavaScript 函数、Redux 相关函数或 React Hook。有时,您会有一些特定于模块的小型实用函数。在这种情况下,您可以将它们放在actions目录中,或者为模块创建专用utils目录。项目的模块结构如下所示。

users/
  actions/
  components/
config/
    constants.js
    routes.js
    tables.js
    forms.js
  pages/
gql/
  schemas/
  index.js
Enter fullscreen mode Exit fullscreen mode

与应用层类似,我们可以包含仅与本模块相关的静态代码(例如常量或模式定义)。在这种情况下,我们将这些代码放在configschema目录中。使用 GraphQL 时,我们可以包含查询和变异定义。这些定义应该放在gql目录(或具有类似用途的目录)中。使用此模块的应用商店时,请添加一个interfaces.js文件。该文件描述了如何访问商店中的数据。

充当目录的index.js这里我们描述了其他人可以访问的所有组件、操作和常量。index.jsapp

模块通信

并非每个模块都需要包含上述所有目录和文件。例如,有些模块不需要页面,因为它们只包含组件和操作。一个很好的例子是“文件”模块。该模块可以组合组件和操作来查看和上传文件。例如,一个文件拖放区域可以将结果上传到 Blob 存储。这可能是一个可重用的组件。然而,文件的实际上传取决于我们可以使用的服务。通过将 UI 组件和实际的文件上传操作结合起来,我们创建了一个小型的包含模块。当我们将组件与业务逻辑结合起来时,我们就将它们转换为模块。

但是其他模块如何使用文件模块中的组件或操作呢?index.js模块的文件描述了哪些组件、操作和常量可供其他组件访问。因此,我们可以使用文件模块中的文件拖放区或上传操作。但是,有时我们必须选择向其他模块公开的内容。是选择操作,还是将操作组合成组件?

让我们看一个用户下拉菜单的示例。我们可以创建一个操作,从不同的模块中提供所有可选用户。但是,现在我们需要在所有其他模块中创建一个特定的下拉菜单。创建一个通用的下拉组件可能不需要太多精力,但这个组件可能无法在表单中使用。创建一个UserDropdown可用的组件或许值得投入一些精力。当用户周围的情况发生变化时,我们现在只需更改一个组件。所以有时我们需要选择要公开的内容:操作还是组件。

使用 PubSub

我们可以在组件之间使用的一种高级模式是使用pubsub。使用此模式,我们无法共享组件,但可以共享数据。上图展示了它的工作原理。再次强调,这是一种高级模式,仅当您想走微前端路线或需要时才使用它。

UI 组件解剖

最后一个细节层面仍然缺失,那就是 UI 组件的架构。我在之前的一篇博文中已经描述过这一点。当你回顾这个架构时,你会看到一些我们在更大规模上应用的概念。

UI 组件解剖

前端是我们用户的第一个入口。随着我们前端项目功能的不断增加,我们也将引入更多错误。但我们的用户期望没有错误,并且能够快速获得新功能。这是不可能的。然而,通过使用良好的架构,我们只能尽力实现这一点。

本文最初发布于kevtiq.co

文章来源:https://dev.to/vyckes/how-to-create-a-scalable-and-maintainable-front-end-architecture-4f47
PREV
为科技社区做贡献:开源如何帮你找到工作并摆脱技能悖论💼
NEXT
理解 JWT 身份验证:包含示例的综合指南