一种不同的前端架构方法

2025-05-25

一种不同的前端架构方法

本文旨在介绍一种易于推理且可维护性高的前端架构(适用于使用 Vue、React、Svelte 等构建的应用程序)。如果您正在构建一个中大型应用程序,并且经常感到困惑,那么本文可能会对您有所帮助。

良好架构的好处

在深入研究任何技术问题之前,让我们先解决一个小问题:

(图片来源: https: //pusher.com/tutorials/clean-architecture-introduction

在上图中,你能一眼看出如何用胶带代替订书机吗?有些人可能会想出一个有趣的办法,但对于我们大多数人来说,我们无法立即想出如何解决这个问题。它在我们眼中看起来乱糟糟的,而且会让我们大脑混乱。

现在看看这个:

(图片来源: https: //pusher.com/tutorials/clean-architecture-introduction

你现在能立刻告诉我怎么把订书机换掉吗?我们只需要解开连接订书机的绳子,然后把胶带放回去就行了。几乎不需要任何脑力劳动就能搞定。

想象一下,上图中的所有项目都是软件中的模块或部件。一个好的架构应该更像第二种布局。这种架构的好处是:

  • 减少您在执行项目时的认知负荷/脑力劳动。
  • 使您的代码更加模块化、松散耦合,从而更易于测试和维护。
  • 简化替换架构中特定部分的过程。

通用前端架构

目前分离前端应用程序最基本、最常见的方法可能是这样的:

通用前端架构

上面的架构乍一看没什么问题。但是,这种架构中出现了一种常见的模式,即把架构的某些部分紧密耦合在一起。例如,这是一个用 Vue 3 和 Vuex 4 编写的简单计数器应用程序:



<template>
  <p>The count is {{ counterValue }}</p>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</template>

<script lang="ts">
import { computed } from 'vue';
import { useStore } from 'vuex';

export default {
  name: 'Counter',
  setup() {
    const store = useStore();
    const count = computed<number>(() => store.getters.count);

    const increment = () => {
      store.dispatch('increment');
    };

    const decrement = () => {
      store.dispatch('decrement');
    };

    return {
      count,
      increment,
      decrement
    };
  }
}
</script>


Enter fullscreen mode Exit fullscreen mode

你会发现,这在使用 Vue 3 和 Vuex 编写的应用程序中是一种相当常见的模式,因为它在Vuex 4 的指南中有所介绍。实际上,这也是 React 与 Redux 或 Svelte 与 Svelte Stores 的常见模式:

  • React 和 Redux 的示例:


import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';

export const CounterComponent = () => {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  const increment = () => {
    dispatch({ type: 'increment' });
  };

  const decrement = () => {
    dispatch({ type: 'decrement' });
  };

  return (
    <div>
      <p>The count is {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
};


Enter fullscreen mode Exit fullscreen mode
  • Svelte 和 Svelte Stores 的示例:


<script>
  import { count } from './stores.js';

  function increment() {
    count.update(n => n + 1);
  }

  function decrement() {
    count.update(n => n - 1);
  }
</script>

<p>The count is {$count}</p>
<button on:click={increment}>+</button>
<button on:click={decrement}>-</button>


Enter fullscreen mode Exit fullscreen mode

这些本质上没有什么问题。事实上,大多数中大型应用程序可能都是这样写的。这些是官方指南/教程中推荐的方法。

然而,凡事皆有取舍。那么,这种模式到底有哪些优点和缺点呢?

最明显的好处可能就是简单。

但为了这个,你牺牲了什么?

您已经将 store 与组件紧密耦合。那么,如果有一天您的团队发现 Redux 不再适合该应用程序(可能是因为它过于复杂),并且想要切换到其他版本,该怎么办?您不仅需要重写所有 store,还需要重写与 Redux 紧密耦合的 React 组件的逻辑。

同样的问题也发生在应用程序中的所有其他层。最终,你无法轻易地用其他部分替换应用程序的某个部分,因为所有部分都紧密耦合在一起。最好的办法是保持现状,从头重写所有内容。

但事实并非如此。真正的模块化架构可以让你用 React + MobX(或 Valtio)替换你的 React + Redux 应用程序,甚至更疯狂的是,用 React + Vuex 或 Vue + Redux(无论出于何种原因)替换,而不会影响应用程序的其他部分

那么,我们如何在不影响其余部分的情况下替换应用程序的一部分,或者换句话说,我们如何将应用程序的每个部分彼此分离?

引入不同的方法

引入不同的架构
各层的特点如下:

  • 表示层:这一层主要由 UI 组件构成。对于 Vue 来说,它们是 Vue SFc。对于 React 来说,它们是 React 组件。对于 Svelte 来说,它们是 Svelte SFC。等等。表示层直接与应用层耦合。
  • 应用程序层:此层包含应用程序逻辑。它了解领域层和基础设施层。在此架构中,此层通过 React 中的 React Hooks 或 Vue 3 中的 Vue Hooks 实现。
  • 领域层:此层用于领域/业务逻辑。领域层仅包含业务逻辑,因此这里只有纯 JavaScript/TypeScript 代码,没有任何框架/库。
  • 基础设施层:此层负责与外界通信(发送请求/接收响应)并存储本地数据。以下是您在实际应用中将使用此层的库的示例:
    • HTTP 请求/响应:Axios、Fetch API、Apollo Client 等。
    • Store(状态管理):Vuex、Redux、MobX、Valtio 等

应用架构

如果将此架构应用于应用程序,它看起来是这样的:

将架构应用于 React 应用程序

从上面的架构图可以看出以下特点:

  • 当您替换 UI 库/框架时,只有表示层和应用程序层会受到影响。
  • 在基础架构层,我们有一个Facade,这样当您替换 store 的实现细节(例如,用 Vuex 替换 Redux)时,只有 store 本身会受到影响。用 Fetch API 替换 Axios 或反之亦然。应用层不知道 store 或 HTTP 客户端的实现细节。换句话说,我们将 React 与 Redux/Vuex/MobX 解耦。store 的逻辑也足够通用,不仅可以与 React 一起使用,还可以与 Vue 或 Svelte 一起使用。
  • 如果业务逻辑发生变化,则必须相应地修改领域层,这将影响架构中的其他部分。

这个架构更有趣的是,你甚至可以进一步模块化它:

进一步模块化架构

注意事项

尽管这种架构可以将应用程序的各个部分解耦,但它也带来了代价:复杂性增加。因此,如果你正在开发一个小型应用程序,我不建议使用这种架构。“莫用大锤砸核桃”

对于更复杂的应用程序,这种架构可能有助于您实现如下目标:

投资架构的好处

(图片来源: https: //www.simform.com/react-architecture-best-practices

一个例子

我构建了一个简单的计数器应用,演示了这种架构的优点。您可以在这里查看源代码:https://github.com/itswillta/flexible-counter-app

一个例子

在这个应用程序中,我引入了 Vue、React 以及 Vuex、Redux、MobX、Valtio 甚至 localStorage。它们都可以互相替换,互不影响。请按照 README 文件中的简单说明,尝试将应用程序的一部分替换为另一部分。

我知道对于这个计数器应用程序,我正在用大锤砸开一个坚果,但是构建一个复杂的应用程序对我来说现在有点不可能。

非常欢迎提问和讨论😊。


如果您对前端开发和 Web 开发感兴趣,请关注我并查看下面个人资料中我的文章。

文章来源:https://dev.to/itswillt/a- Different-approach-to-frontend-architecture-38d4
PREV
⚛️ 在 React 中应用策略模式(第一部分)
NEXT
两个新的 React 框架