你在前端使用架构反模式吗?

发布: (2026年1月8日 GMT+8 02:54)
12 min read
原文: Dev.to

Source: Dev.to

请提供您想要翻译的正文内容,我将把它翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。谢谢!

什么是软件架构?

软件架构是软件系统的高级结构,创建此类结构的学科,以及这些结构的文档。Software Architecture in Practice, Bass 等。

用简单的话说,架构是 定义系统中各个组件如何组织以及它们之间如何交互的结构。此外,它还规定了指导开发的 规则和原则

一个好的架构:

  • 为所有开发者提供一致的工作流程。
  • 允许新团队或成员无障碍加入项目。
  • 使代码库更具 可维护性可扩展性可测试性
  • 提升软件质量和团队生产力。

良好架构的基本支柱

支柱描述实践示例
职责分离每个模块只有单一职责。UI 组件与业务逻辑分离。
低耦合模块之间的依赖尽可能少。使用接口和抽象。
高内聚相关元素被归为一组。功能或领域定义明确。
可扩展性系统可以在不进行大幅重构的情况下扩展。模块化架构。
可测试性编写和执行测试的便利性。可注入的依赖。

架构反模式

反模式是对常见问题的常见回应,通常无效且有高度适得其反的风险。《反模式:危机中的软件、架构和项目重构》

假设 vs. 反假设

假设反假设
良好的架构保持整个开发团队的工作流顺畅。架构反模式可能导致整个开发团队的工作流不畅。
良好的架构有助于新成员加入开发团队。架构反模式可能使新成员加入开发团队变得困难。
良好的架构使代码库更易维护、可扩展且可测试。架构反模式可能使代码库的可维护性、可扩展性和可测试性下降。
良好的架构提升软件质量和开发团队的生产力。架构反模式可能降低软件质量和开发团队的生产力。

前端最常见的反模式

1. God Component(神组件)

症状

  • 没有明确的层次划分。
  • 组件承担多重职责(超过 500 行)。
  • 模块之间出现循环依赖。
  • 难以定位某项功能的逻辑所在。
  • 在一个地方的改动会导致其他地方的功能失效。

神组件示例(不良)

// UserDashboard.jsx - 800+ 行
function UserDashboard() {
  const [user, setUser] = useState(null);
  const [orders, setOrders] = useState([]);
  const [notifications, setNotifications] = useState([]);
  const [settings, setSettings] = useState({});
  const [isLoading, setIsLoading] = useState(true);
  // ... 50 更多状态

  useEffect(() => {
    // Fetch user, orders, notifications, settings...
    // 数据转换逻辑
    // 错误处理
    // WebSocket 连接
  }, [/* 很多依赖 */]);

  const handleUpdateProfile = async () => { /* ... */ };
  const handleDeleteOrder = async () => { /* ... */ };
  const handleMarkNotificationRead = async () => { /* ... */ };
  // ... 30 更多处理函数

  return (
    // 500+ 行 JSX
  );
}

解决方案 – 组合与分离

  1. 拆分 UI 为小而可复用的组件(例如 UserProfileOrderListNotificationPanel)。
  2. 将业务逻辑和数据访问外部化hooks 或独立服务(useUseruseOrdersapiClient)。
  3. 使用合适的方案管理全局状态(Redux、Zustand、Context API),仅在局部保留 UI 状态。
  4. 对每个文件/模块应用单一职责原则
// UserDashboard.jsx – 组合
import UserProfile from './UserProfile';
import OrderList from './OrderList';
import NotificationPanel from './NotificationPanel';
import SettingsPanel from './SettingsPanel';

export default function UserDashboard() {
  return (
    <>
      <UserProfile />
      <OrderList />
      <NotificationPanel />
      <SettingsPanel />
    </>
  );
}
// useUser.js – 业务逻辑 hook
import { useState, useEffect } from 'react';
import apiClient from '../api/apiClient';

export function useUser() {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    apiClient.getUser()
      .then(data => setUser(data))
      .finally(() => setIsLoading(false));
  }, []);

  return { user, isLoading };
}

结论

架构反模式会不经意间出现,但识别它们的 症状 并采用 基于职责分离、低耦合和高内聚的解决方案 可以保持代码的整洁、可扩展且易于测试。

通过从一开始就采用良好实践并定期审查项目结构,我们将避免前端变成“意大利面条代码”,并确保更高效、更可持续的开发体验。

前端架构反模式

1. 级联依赖

“它变成了一场噩梦。”

症状

  • 更改一个组件需要修改许多其他组件。
  • 测试需要对大量依赖进行 mock。
  • 组件直接从其他模块的深层路径导入。
  • 过度 Props drilling(将 props 通过 5 层以上传递)。

解决方案 – 依赖倒置

创建一个充当 杂物抽屉 的文件夹,用来放置那些“不知道放在哪里”的模块。

问题结构(不佳)

src/
├── utils/
│   ├── helpers.js          # 2000 行
│   ├── functions.js        # 1500 行
│   ├── misc.js             # ???
│   ├── common.js           # 更多相同内容
│   └── index.js            # 重新导出所有

2. 全局状态过度

“把全局状态用于所有东西,甚至是应该是局部的状态。”

症状

  • 全局 store 拥有 数百个属性
  • 在 Redux/Zustand 中存放表单状态。
  • 将临时或 UI 值放在全局状态中。
  • 难以追踪是谁修改了什么。
  • 整个应用出现不必要的重新渲染。

解决方案 – 状态职责分离

避免在不需要时就创建复杂抽象(“以防万一”)。

常见原因

原因描述
最近阅读了某个模式想在没有真实需求的情况下使用它。
害怕未来的重构倾向于“预先优化”。
受到“专业代码”的压力认为复杂度等同于质量。
未区分本质复杂度与偶然复杂度添加没有价值的层。

3. 抽象的过度工程

“在需要之前就创建复杂抽象。”

自检问题

问题如果答案是 NO
我现在真的需要这个抽象吗?等到有真实用例再说。
我是在解决真实问题还是假设性问题?不要解决不存在的问题。
这个抽象是增值还是仅增加复杂度?保持解决方案简洁。
我能先用更简单的方式实现吗?先保持简单,后续再重构。

三次原则

当一种解决方案重复 三次 时,就值得抽象出来。

不佳 – 为一个简单按钮进行过度工程

// 8 文件,200+ 行来……一个按钮
interface IButtonStrategy {
  execute(): void;
}

interface IButtonProps {
  strategy: IButtonStrategy;
  builder: IButtonBuilder;
}

class SubmitButtonStrategy implements IButtonStrategy {
  constructor(private validator: IFormValidator) {}
  execute(): void { /* ... */ }
}

class CancelButtonStrategy implements IButtonStrategy { /* ... */ }
class ButtonStrategyFactory { /* ... */ }
class ButtonBuilder implements IButtonBuilder { /* ... */ }
class AbstractButton extends BaseComponent { /* ... */ }

// 最终渲染:
// Submit

佳 – 解决真实问题的简易方案

// 一个组件,约 30 行
interface ButtonProps {
  variant: 'submit' | 'cancel' | 'default';
  onClick: () => void;
  children: React.ReactNode;
  disabled?: boolean;
}

function Button({ variant, onClick, children, disabled }: ButtonProps) {
  const styles = {
    submit: 'bg-blue-500 text-white',
    cancel: 'bg-gray-200 text-gray-700',
    default: 'bg-white border border-gray-300',
  };

  return (
    <button
      className={styles[variant]}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

“让它能工作,让它正确,让它快速。” — Kent Beck
“复制的成本远低于错误的抽象。” — Sandi Metz

4. 不必要的重复 vs. 过早抽象

“在确实需要时仍然复制粘贴代码,而不是创建可复用的抽象。”

症状

  • 多个组件的代码几乎相同。
  • 需要修改大量文件的更改。
  • 在一个地方修复的 bug 在其他地方仍然存在。

评估检查表

问题✅ 是❌ 否
新开发者能理解结构吗?
如果你标记了多个否,则表明存在反模式。

5. 渐进式重构策略

  1. 记录你的架构

    • 使用 ADRs(架构决策记录)来记录每个决定的“原因”。
  2. 建立约定

    • 定义清晰且一致的文件夹结构。
  3. 团队代码审查

    • 代码审查 有助于及早发现反模式。
  4. 度量与监控

    • 工具:SonarQube、ESLint(复杂度规则)、覆盖率指标。
  5. 向他人学习

    • 研究成熟的架构:Feature‑Sliced DesignAtomic Design、适用于前端的 Clean Architecture

结论

架构反模式就像 沉默的技术债务:我们往往在项目变得难以维护时才注意到它们。

避免它们的关键

  • 及早发现 – 定期检查代码健康状况。
  • 持续学习 – 学习架构模式。
  • 与团队沟通 – 架构决策是团队决策。
  • 保持平衡 – 既不要过度工程,也不要出现 spaghetti 代码。

你在项目中发现过这些反模式吗?
请在评论中分享你的经验!

参考文献

  • Clean Architecture – Robert C. Martin
  • Patterns of Enterprise Application Architecture – Martin Fowler
  • AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis – Brown et al.

你喜欢这篇文章吗?关注我获取更多关于架构和前端开发的内容。

前端架构与开发最佳实践

Back to Blog

相关文章

阅读更多 »

配置管理

引言 在多租户系统中——例如 SaaS 平台或被多家公司使用的聊天机器人——设计一个强健的配置非常重要……