大型项目模块组织
本章将探讨如何在大型前端项目中进行模块化架构设计,包括项目结构规划、模块间通信、依赖管理和团队协作等方面的最佳实践。
项目背景
我们以一个大型电商平台前端项目为例,该项目具有以下特点:
- 多个团队并行开发
- 功能模块复杂且相互关联
- 需要支持多种业务场景
- 代码库规模超过100万行
- 需要支持微前端架构
整体架构设计
1. 分层架构
大型电商平台
├── 应用层 (Applications)
│ ├── 用户端应用 (Customer App)
│ ├── 商家端应用 (Merchant App)
│ └── 管理端应用 (Admin App)
├── 业务层 (Business)
│ ├── 用户模块 (User)
│ ├── 商品模块 (Product)
│ ├── 订单模块 (Order)
│ ├── 支付模块 (Payment)
│ └── 营销模块 (Marketing)
├── 基础设施层 (Infrastructure)
│ ├── 网络服务 (Network)
│ ├── 状态管理 (State)
│ ├── 路由管理 (Router)
│ └── 缓存服务 (Cache)
└── 通用层 (Common)
├── UI组件库 (Components)
├── 工具函数 (Utils)
├── 常量定义 (Constants)
└── 类型定义 (Types)
2. 目录结构设计
ecommerce-platform/
├── apps/ # 应用层
│ ├── customer/ # 用户端应用
│ ├── merchant/ # 商家端应用
│ └── admin/ # 管理端应用
├── packages/ # 共享包
│ ├── business/ # 业务模块
│ │ ├── user/
│ │ ├── product/
│ │ ├── order/
│ │ ├── payment/
│ │ └── marketing/
│ ├── infrastructure/ # 基础设施
│ │ ├── network/
│ │ ├── state/
│ │ ├── router/
│ │ └── cache/
│ ├── ui/ # UI组件库
│ │ ├── components/
│ │ ├── themes/
│ │ └── styles/
│ └── common/ # 通用工具
│ ├── utils/
│ ├── constants/
│ ├── types/
│ └── hooks/
├── tools/ # 开发工具
│ ├── build/
│ ├── eslint-config/
│ └── test-utils/
├── docs/ # 文档
└── scripts/ # 脚本
模块设计原则
1. 领域驱动设计
每个业务模块按照领域驱动设计(DDD)原则组织:
// packages/business/user/
├── src/
│ ├── domain/ # 领域层
│ │ ├── entities/ # 实体
│ │ │ ├── User.ts
│ │ │ └── Profile.ts
│ │ ├── valueObjects/ # 值对象
│ │ │ ├── Email.ts
│ │ │ └── Phone.ts
│ │ ├── services/ # 领域服务
│ │ │ └── UserService.ts
│ │ └── repositories/ # 仓储接口
│ │ └── UserRepository.ts
│ ├── infrastructure/ # 基础设施层
│ │ ├── repositories/ # 仓储实现
│ │ │ └── ApiUserRepository.ts
│ │ └── services/ # 外部服务
│ │ └── AuthService.ts
│ ├── application/ # 应用层
│ │ ├── useCases/ # 用例
│ │ │ ├── CreateUser.ts
│ │ │ ├── UpdateProfile.ts
│ │ │ └── GetUserById.ts
│ │ └── dtos/ # 数据传输对象
│ │ ├── CreateUserDto.ts
│ │ └── UpdateProfileDto.ts
│ └── presentation/ # 表示层
│ ├── components/ # 组件
│ ├── hooks/ # 钩子
│ └── stores/ # 状态管理
└── package.json
2. 模块边界定义
// packages/business/user/src/index.ts
// 只导出应用层和表示层的公共接口
export { CreateUser, UpdateProfile, GetUserById } from './application/useCases';
export { CreateUserDto, UpdateProfileDto } from './application/dtos';
export { UserProfile, UserSettings } from './presentation/components';
export { useUser, useUserProfile } from './presentation/hooks';
export { userStore } from './presentation/stores';
// 类型定义
export type { User, Profile } from './domain/entities';
export type { Email, Phone } from './domain/valueObjects';
3. 依赖注入配置
// packages/business/user/src/container.ts
import { Container } from 'inversify';
import { UserRepository } from './domain/repositories/UserRepository';
import { ApiUserRepository } from './infrastructure/repositories/ApiUserRepository';
import { CreateUser } from './application/useCases/CreateUser';
const userContainer = new Container();
// 绑定依赖
userContainer.bind<UserRepository>('UserRepository').to(ApiUserRepository);
userContainer.bind<CreateUser>('CreateUser').to(CreateUser);
export { userContainer };
模块间通信策略
1. 事件驱动架构
// packages/infrastructure/events/src/EventBus.ts
export interface Event {
type: string;
payload: any;
timestamp: Date;
source: string;
}
export class EventBus {
private listeners: Map<string, Set<(event: Event) => void>> = new Map();
subscribe(eventType: string, listener: (event: Event) => void): void {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set());
}
this.listeners.get(eventType)!.add(listener);
}
unsubscribe(eventType: string, listener: (event: Event) => void): void {
this.listeners.get(eventType)?.delete(listener);
}
publish(event: Event): void {
const listeners = this.listeners.get(event.type);
if (listeners) {
listeners.forEach(listener => listener(event));
}
}
}
// 单例实例
export const eventBus = new EventBus();
2. 跨模块通信示例
// packages/business/order/src/application/useCases/CreateOrder.ts
import { eventBus } from '@platform/infrastructure/events';
import { Order } from '../domain/entities/Order';
export class CreateOrder {
async execute(orderData: CreateOrderDto): Promise<Order> {
const order = await this.orderRepository.create(orderData);
// 发布订单创建事件
eventBus.publish({
type: 'ORDER_CREATED',
payload: { orderId: order.id, userId: order.userId },
timestamp: new Date(),
source: 'order-module'
});
return order;
}
}
// packages/business/user/src/application/useCases/UpdateUserPoints.ts
import { eventBus } from '@platform/infrastructure/events';
export class UpdateUserPoints {
constructor() {
// 监听订单创建事件
eventBus.subscribe('ORDER_CREATED', this.handleOrderCreated.bind(this));
}
private async handleOrderCreated(event: Event): Promise<void> {
const { userId } = event.payload;
await this.userRepository.addPoints(userId, 100);
}
}
状态管理架构
1. 分层状态管理
// packages/infrastructure/state/src/Store.ts
import { configureStore } from '@reduxjs/toolkit';
import { userSlice } from '@platform/business/user';
import { productSlice } from '@platform/business/product';
import { orderSlice } from '@platform/business/order';
export const store = configureStore({
reducer: {
// 业务状态
user: userSlice.reducer,
product: productSlice.reducer,
order: orderSlice.reducer,
// 应用状态
ui: uiSlice.reducer,
router: routerSlice.reducer,
// 基础设施状态
network: networkSlice.reducer,
cache: cacheSlice.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST']
}
}).concat(
// 自定义中间件
eventMiddleware,
cacheMiddleware,
loggerMiddleware
)
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
2. 模块状态隔离
// packages/business/user/src/presentation/stores/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface UserState {
currentUser: User | null;
profile: Profile | null;
loading: boolean;
error: string | null;
}
const initialState: UserState = {
currentUser: null,
profile: null,
loading: false,
error: null
};
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser: (state, action: PayloadAction<User>) => {
state.currentUser = action.payload;
},
setProfile: (state, action: PayloadAction<Profile>) => {
state.profile = action.payload;
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setError: (state, action: PayloadAction<string>) => {
state.error = action.payload;
}
}
});
export const { setUser, setProfile, setLoading, setError } = userSlice.actions;
组件库设计
1. 设计系统组件
// packages/ui/components/src/Button/Button.tsx
import React from 'react';
import { styled } from '@platform/ui/themes';
export interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
const StyledButton = styled.button<ButtonProps>`
/* 使用设计系统的token */
font-family: ${({ theme }) => theme.typography.fontFamily};
font-size: ${({ theme, size }) => theme.typography.fontSize[size || 'medium']};
padding: ${({ theme, size }) => theme.spacing.button[size || 'medium']};
border-radius: ${({ theme }) => theme.borderRadius.medium};
/* 变体样式 */
${({ theme, variant }) => {
switch (variant) {
case 'primary':
return `
background-color: ${theme.colors.primary[500]};
color: ${theme.colors.white};
border: none;
`;
case 'secondary':
return `
background-color: transparent;
color: ${theme.colors.primary[500]};
border: 1px solid ${theme.colors.primary[500]};
`;
case 'danger':
return `
background-color: ${theme.colors.danger[500]};
color: ${theme.colors.white};
border: none;
`;
default:
return '';
}
}}
`;
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
disabled = false,
loading = false,
children,
onClick
}) => {
return (
<StyledButton
variant={variant}
size={size}
disabled={disabled || loading}
onClick={onClick}
>
{loading ? 'Loading...' : children}
</StyledButton>
);
};
2. 复合组件模式
// packages/ui/components/src/DataTable/DataTable.tsx
import React from 'react';
interface DataTableProps<T> {
data: T[];
children: React.ReactNode;
}
interface DataTableHeaderProps {
children: React.ReactNode;
}
interface DataTableBodyProps {
children: React.ReactNode;
}
interface DataTableRowProps<T> {
item: T;
children: (item: T) => React.ReactNode;
}
function DataTable<T>({ data, children }: DataTableProps<T>) {
return (
<table className="data-table">
{children}
</table>
);
}
function DataTableHeader({ children }: DataTableHeaderProps) {
return (
<thead>
<tr>{children}</tr>
</thead>
);
}
function DataTableBody<T>({ children }: DataTableBodyProps) {
return <tbody>{children}</tbody>;
}
function DataTableRow<T>({ item, children }: DataTableRowProps<T>) {
return <tr>{children(item)}</tr>;
}
// 复合组件导出
DataTable.Header = DataTableHeader;
DataTable.Body = DataTableBody;
DataTable.Row = DataTableRow;
export { DataTable };
性能优化策略
1. 代码分割策略
// apps/customer/src/routes/index.tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { LoadingSpinner } from '@platform/ui/components';
// 路由级别的代码分割
const HomePage = lazy(() => import('../pages/HomePage'));
const ProductPage = lazy(() => import('../pages/ProductPage'));
const OrderPage = lazy(() => import('../pages/OrderPage'));
const UserPage = lazy(() => import('../pages/UserPage'));
export function AppRoutes() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products/*" element={<ProductPage />} />
<Route path="/orders/*" element={<OrderPage />} />
<Route path="/user/*" element={<UserPage />} />
</Routes>
</Suspense>
);
}
2. 模块级别的懒加载
// packages/business/product/src/index.ts
import { lazy } from 'react';
// 懒加载复杂组件
export const ProductCatalog = lazy(() => import('./presentation/components/ProductCatalog'));
export const ProductDetail = lazy(() => import('./presentation/components/ProductDetail'));
// 立即导出轻量级模块
export { useProduct } from './presentation/hooks/useProduct';
export { productStore } from './presentation/stores/productSlice';
export type { Product, ProductCategory } from './domain/entities';
3. 缓存策略
// packages/infrastructure/cache/src/CacheManager.ts
export class CacheManager {
private cache = new Map<string, { data: any; expiry: number }>();
set<T>(key: string, data: T, ttl: number = 5 * 60 * 1000): void {
this.cache.set(key, {
data,
expiry: Date.now() + ttl
});
}
get<T>(key: string): T | null {
const cached = this.cache.get(key);
if (!cached) return null;
if (Date.now() > cached.expiry) {
this.cache.delete(key);
return null;
}
return cached.data as T;
}
invalidate(pattern: string): void {
const regex = new RegExp(pattern);
for (const key of this.cache.keys()) {
if (regex.test(key)) {
this.cache.delete(key);
}
}
}
}
export const cacheManager = new CacheManager();
构建和部署
1. Monorepo构建配置
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}
2. 微前端部署策略
// tools/build/webpack.config.js
const ModuleFederationPlugin = require('@module-federation/webpack');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'customer_app',
filename: 'remoteEntry.js',
exposes: {
'./App': './src/App',
'./routes': './src/routes'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
'@platform/ui': { singleton: true }
}
})
]
};
团队协作
1. 模块所有权
# .github/CODEOWNERS
# 业务模块所有权
/packages/business/user/ @user-team
/packages/business/product/ @product-team
/packages/business/order/ @order-team
/packages/business/payment/ @payment-team
# 基础设施
/packages/infrastructure/ @platform-team
/packages/ui/ @design-system-team
# 应用
/apps/customer/ @frontend-team
/apps/merchant/ @merchant-team
/apps/admin/ @admin-team
2. 开发工作流
// scripts/dev-workflow.ts
export class DevWorkflow {
async runTests(modules: string[]): Promise<void> {
// 只运行相关模块的测试
for (const module of modules) {
await this.runModuleTests(module);
}
}
async buildAffectedModules(changedFiles: string[]): Promise<void> {
const affectedModules = this.getAffectedModules(changedFiles);
await this.buildModules(affectedModules);
}
private getAffectedModules(changedFiles: string[]): string[] {
// 基于依赖图分析影响的模块
return this.dependencyGraph.getAffected(changedFiles);
}
}
监控和维护
1. 模块健康度监控
// tools/monitoring/src/ModuleHealthChecker.ts
export class ModuleHealthChecker {
checkDependencyHealth(): HealthReport {
const report: HealthReport = {
circularDependencies: this.findCircularDependencies(),
unusedDependencies: this.findUnusedDependencies(),
outdatedDependencies: this.findOutdatedDependencies(),
bundleSize: this.analyzeBundleSize()
};
return report;
}
generateReport(): void {
const health = this.checkDependencyHealth();
console.log('模块健康度报告:', health);
}
}
2. 性能监控
// packages/infrastructure/monitoring/src/PerformanceMonitor.ts
export class PerformanceMonitor {
trackModuleLoad(moduleName: string): void {
const startTime = performance.now();
// 监控模块加载时间
import(moduleName).then(() => {
const loadTime = performance.now() - startTime;
this.reportMetric('module_load_time', loadTime, { module: moduleName });
});
}
trackComponentRender(componentName: string, renderTime: number): void {
this.reportMetric('component_render_time', renderTime, { component: componentName });
}
private reportMetric(metric: string, value: number, tags: Record<string, string>): void {
// 发送到监控系统
analytics.track(metric, value, tags);
}
}
最佳实践总结
1. 架构设计原则
- 单一职责:每个模块只负责一个业务领域
- 依赖倒置:依赖抽象而不是具体实现
- 开放封闭:对扩展开放,对修改封闭
- 接口隔离:客户端不应该依赖它不需要的接口
2. 模块化策略
- 清晰的边界:明确定义模块的输入和输出
- 松耦合:减少模块间的直接依赖
- 高内聚:相关功能应该在同一个模块内
- 可测试性:模块应该易于单独测试
3. 团队协作
- 模块所有权:每个模块有明确的负责团队
- API契约:模块间通过稳定的API进行交互
- 版本管理:使用语义化版本管理模块更新
- 文档维护:保持API文档和架构文档的更新
4. 性能优化
- 代码分割:按需加载模块和组件
- 缓存策略:合理使用各种缓存机制
- 包体积优化:避免重复依赖和无用代码
- 运行时优化:使用虚拟列表、memo等技术
通过这套完整的大型项目模块化方案,我们可以构建出可维护、可扩展、高性能的企业级前端应用。
下一章: 模块懒加载实现 →