你在前端使用架构反模式吗?
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
);
}
解决方案 – 组合与分离
- 拆分 UI 为小而可复用的组件(例如
UserProfile、OrderList、NotificationPanel)。 - 将业务逻辑和数据访问外部化 为 hooks 或独立服务(
useUser、useOrders、apiClient)。 - 使用合适的方案管理全局状态(Redux、Zustand、Context API),仅在局部保留 UI 状态。
- 对每个文件/模块应用单一职责原则。
// 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. 渐进式重构策略
-
记录你的架构
- 使用 ADRs(架构决策记录)来记录每个决定的“原因”。
-
建立约定
- 定义清晰且一致的文件夹结构。
-
团队代码审查
- 代码审查 有助于及早发现反模式。
-
度量与监控
- 工具:SonarQube、ESLint(复杂度规则)、覆盖率指标。
-
向他人学习
- 研究成熟的架构:Feature‑Sliced Design、Atomic 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.
你喜欢这篇文章吗?关注我获取更多关于架构和前端开发的内容。