前端架构设计
约 1093 字大约 4 分钟
架构设计前端工程化
2026-04-15
概述
前端架构设计是系统性思考前端项目整体结构、技术选型、数据流设计的过程。
架构设计原则
SOLID 原则在前端的应用
| 原则 | 前端实践 |
|---|---|
| 单一职责 | 组件职责单一、 Hook 只做一件事 |
| 开闭原则 | 通过 Props 扩展、通过插槽扩展 |
| 里氏替换 | 组件可替换而不影响功能 |
| 接口隔离 | 组件只依赖必要的 Props |
| 依赖倒置 | 依赖抽象而非具体实现 |
分层架构
经典三层架构
┌─────────────────────────────────────┐
│ 视图层 (View) │
│ React 组件 / Vue 组件 / 模板 │
├─────────────────────────────────────┤
│ 逻辑层 (Logic) │
│ Hooks / Composables / Store │
├─────────────────────────────────────┤
│ 数据层 (Data) │
│ API 服务 / 状态管理 / LocalStorage │
└─────────────────────────────────────┘领域驱动设计 (DDD)
src/
├── domains/ # 领域层
│ ├── user/
│ │ ├── entities/ # 实体
│ │ ├── repositories/ # 仓储接口
│ │ └── services/ # 领域服务
│ └── order/
├── applications/ # 应用层
│ ├── user/
│ │ └── useCases/ # 用例
│ └── order/
├── infrastructures/ # 基础设施层
│ ├── api/
│ └── storage/
└── interfaces/ # 接口层
└── components/状态管理架构
状态分类
| 类型 | 示例 | 管理方案 |
|---|---|---|
| 组件本地状态 | 表单输入、展开/收起 | useState / ref |
| 跨组件状态 | 用户登录信息 | Context / Pinia |
| 全局状态 | 购物车、主题 | 状态管理库 |
| 服务端状态 | API 数据、列表 | React Query / SWR |
| URL 状态 | 搜索参数、筛选条件 | React Router / Vue Router |
状态管理库对比
| 库 | 特点 | 适用场景 |
|---|---|---|
| Redux | 单一 store、不可变数据 | 大型复杂应用 |
| MobX | 响应式、mutable | 中型应用 |
| Zustand | 轻量、简单 | 所有规模 |
| Pinia | Vue 官方、TypeScript 支持 | Vue 项目 |
| Jotai | 原子化状态 | React 项目 |
| Recoil | Facebook 出品 | React 项目 |
组件设计
组件分类模型
┌─────────────────────────────────────────┐
│ 基础组件 │
│ Button / Input / Modal / Table │
│ 特点:无业务逻辑,可直接复用 │
├─────────────────────────────────────────┤
│ 业务组件 │
│ UserCard / ProductList / OrderForm │
│ 特点:轻度业务关联,可跨项目复用 │
├─────────────────────────────────────────┤
│ 页面组件 │
│ Dashboard / Profile / Checkout │
│ 特点:强业务关联,特定项目使用 │
└─────────────────────────────────────────┘组件设计准则
- 单一职责:每个组件只做一件事
- Props 传递:数据自上而下传递
- 回调模式:子组件通过回调通知父组件
- 插槽复用:通过 slot 增强灵活性
- 纯组件:减少不必要的重渲染
复合组件模式
// 组合多个基础组件
function Select({ value, onChange, children }) {
return (
<div className="select">
<Trigger>
<span>{value || '请选择'}</span>
</Trigger>
<Dropdown onChange={onChange}>
{children}
</Dropdown>
</div>
);
}
function Option({ value, children }) {
return <Option value={value}>{children}</Option>;
}
// 使用
<Select value={city} onChange={setCity}>
<Option value="beijing">北京</Option>
<Option value="shanghai">上海</Option>
</Select>API 设计
RESTful API 规范
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /users | 获取用户列表 |
| GET | /users/:id | 获取单个用户 |
| POST | /users | 创建用户 |
| PUT | /users/:id | 更新用户 |
| DELETE | /users/:id | 删除用户 |
API 层封装
// services/user.ts
import { request } from './request';
export interface User {
id: string;
name: string;
email: string;
}
export const userService = {
list: () => request.get<User[]>('/users'),
get: (id: string) => request.get<User>(`/users/${id}`),
create: (data: Omit<User, 'id'>) =>
request.post<User>('/users', data),
update: (id: string, data: Partial<User>) =>
request.put<User>(`/users/${id}`, data),
delete: (id: string) =>
request.delete<void>(`/users/${id}`),
};请求层封装
// services/request.ts
interface ApiResponse<T> {
data: T;
message: string;
code: number;
}
class Request {
private baseURL = import.meta.env.VITE_API_BASE_URL;
async get<T>(url: string): Promise<T> {
const response = await fetch(`${this.baseURL}${url}`);
const result: ApiResponse<T> = await response.json();
if (result.code !== 0) {
throw new Error(result.message);
}
return result.data;
}
async post<T>(url: string, data: unknown): Promise<T> {
const response = await fetch(`${this.baseURL}${url}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result: ApiResponse<T> = await response.json();
return result.data;
}
}
export const request = new Request();性能架构
首屏加载优化
首屏加载时间 = HTML 下载 + JS 下载/解析 + JS 执行 + 首屏渲染| 优化手段 | 实现方式 |
|---|---|
| 代码分割 | React.lazy / Vue Router 懒加载 |
| Tree Shaking | ES Module + 构建工具优化 |
| 压缩 | Terser / Esbuild |
| CDN | 静态资源放到 CDN |
| 缓存 | 浏览器缓存 + 协商缓存 |
运行时性能
| 问题 | 解决方案 |
|---|---|
| 频繁重渲染 | React.memo / Vue computed |
| 大列表卡顿 | 虚拟滚动 (react-virtual / vue-virtual-scroller) |
| 内存泄漏 | 及时清理定时器、事件监听 |
| 长任务阻塞 | Web Worker / requestIdleCallback |
可访问性 (A11Y)
语义化 HTML
<!-- 语义化 -->
<header>导航</header>
<main>
<article>
<h1>标题</h1>
<section>内容区块</section>
</article>
</main>
<footer>底部</footer>
<!-- 非语义化 -->
<div class="header">导航</div>
<div class="main">
<div class="article">
<div class="title">标题</div>
<div class="content">内容区块</div>
</div>
</div>ARIA 属性
<button
aria-label="关闭"
aria-expanded="false"
aria-controls="menu"
>
✕
</button>
<input
type="text"
aria-invalid="true"
aria-describedby="error-message"
/>
<div id="error-message" role="alert">
请输入有效的邮箱地址
</div>