关于 mvc-architecture
mvc-architecture 提供使用 Model-View-Controller 模式组织和维护代码的指南,涵盖各层职责、数据流、测试、反模式及从现有代码库迁移。
主要功能
- 01Model、View 和 Controller 职责
- 02目录结构指引
- 03分层数据流建议
- 04各层测试策略
- 05迁移步骤与反模式
适用场景
- 01规划 MVC 代码库结构
- 02将业务逻辑与 UI 组件分离
- 03独立测试各层
- 04将现有代码库迁移到 MVC
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ds-codi/ project-memory-mcp --skill mvc-architecture安装后在 Claude Code 中输入/mvc-architecture确认已加载。
详细介绍
MVC 架构指南
本文档提供了在代码库中实现和维护模型-视图-控制器(MVC)架构的指南。
核心原则
1. 分离关注点
- 模型:数据结构、业务逻辑和状态管理
- 视图:UI 组件和呈现逻辑
- 控制器:请求处理、协调和数据流
2. 目录结构
src/
├── models/ # Data models and business logic
│ ├── types/ # Type definitions
│ ├── entities/ # Domain entities
│ ├── services/ # Business logic services
│ └── repositories/ # Data access layer
│
├── views/ # UI components
│ ├── components/ # Reusable UI components
│ ├── pages/ # Page-level components
│ ├── layouts/ # Layout components
│ └── styles/ # Stylesheets
│
├── controllers/ # Request handlers and orchestration
│ ├── api/ # API route handlers
│ ├── hooks/ # React hooks (for React apps)
│ └── middleware/ # Request middleware
│
└── utils/ # Shared utilities
├── helpers/ # Helper functions
└── constants/ # Application constants
模型层
职责
- 定义数据结构和类型
- 实现业务规则和验证
- 处理数据持久化和检索
- 管理应用程序状态
最佳实践
// models/entities/User.ts
export interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
// models/services/UserService.ts
export class UserService {
constructor(private repository: UserRepository) {}
async createUser(data: CreateUserDTO): Promise<User> {
// Business logic validation
if (!this.isValidEmail(data.email)) {
throw new ValidationError('Invalid email');
}
return this.repository.create(data);
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
// models/repositories/UserRepository.ts
export interface UserRepository {
create(data: CreateUserDTO): Promise<User>;
findById(id: string): Promise<User | null>;
update(id: string, data: UpdateUserDTO): Promise<User>;
delete(id: string): Promise<void>;
}
指南
- 保持模型纯粹,不包含 UI 逻辑
- 使用依赖注入来提高可测试性
- 使用仓库模式访问数据
- 为所有服务定义清晰的接口
视图层
职责
- 渲染 UI 组件
- 处理用户交互
- 显示模型中的数据
- 仅管理组件本地状态
最佳实践
// views/components/UserCard.tsx
interface UserCardProps {
user: User;
onEdit: () => void;
onDelete: () => void;
}
export function UserCard({ user, onEdit, onDelete }: UserCardProps) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<div className="actions">
<button onClick={onEdit}>Edit</button>
<button onClick={onDelete}>Delete</button>
</div>
</div>
);
}
// views/pages/UsersPage.tsx
export function UsersPage() {
const { users, isLoading, error, deleteUser } = useUsers();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div className="users-page">
<h1>Users</h1>
<UserList users={users} onDelete={deleteUser} />
</div>
);
}
指南
- 保持组件小巧且职责明确
- 优先使用组合而非继承
- Props 应保持不可变
- 避免在视图中编写业务逻辑
- 适当时使用展示层/容器模式
控制器层
职责
- 处理传入请求
- 协调模型和视图
- 管理数据流
- 处理错误和边界情况
最佳实践
// controllers/hooks/useUsers.ts
export function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
setIsLoading(true);
const data = await userService.getAll();
setUsers(data);
} catch (err) {
setError(err as Error);
} finally {
setIsLoading(false);
}
};
const deleteUser = async (id: string) => {
await userService.delete(id);
setUsers(users.filter(u => u.id !== id));
};
return { users, isLoading, error, deleteUser, refetch: fetchUsers };
}
// controllers/api/usersController.ts (for backend)
export class UsersController {
constructor(private userService: UserService) {}
async getAll(req: Request, res: Response) {
try {
const users = await this.userService.getAll();
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
}
async create(req: Request, res: Response) {
try {
const user = await this.userService.createUser(req.body);
res.status(201).json(user);
} catch (error) {
if (error instanceof ValidationError) {
res.status(400).json({ error: error.message });
} else {
res.status(500).json({ error: 'Failed to create user' });
}
}
}
}
指南
- 控制器应保持精简——将工作委托给服务
- 处理所有错误情况
- 将输入传递给模型前先进行验证
- 使用中间件处理横切关注点
数据流
User Action → View → Controller → Model → Controller → View → Updated UI
│ │ │ │ │ │
│ │ │ │ │ └─ Re-render
│ │ │ │ └─ Update state
│ │ │ └─ Business logic
│ │ └─ Handle request
│ └─ Event handler
└─ Click/Input
测试策略
模型测试
describe('UserService', () => {
it('should validate email before creating user', async () => {
const service = new UserService(mockRepository);
await expect(service.createUser({ email: 'invalid' }))
.rejects.toThrow('Invalid email');
});
});
视图测试
describe('UserCard', () => {
it('should display user information', () => {
render(<UserCard user={mockUser} onEdit={jest.fn()} onDelete={jest.fn()} />);
expect(screen.getByText(mockUser.name)).toBeInTheDocument();
});
});
控制器测试
describe('useUsers', () => {
it('should fetch users on mount', async () => {
const { result } = renderHook(() => useUsers());
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current.users).toHaveLength(2);
});
});
应避免的常见反模式
❌ 在视图中编写业务逻辑
// BAD
function UserList({ users }) {
const activeUsers = users.filter(u => u.status === 'active' && u.lastLogin > Date.now() - 86400000);
// ...
}
✅ 移至模型/服务
// GOOD
function UserList({ users }) {
const activeUsers = userService.getActiveUsers(users);
// ...
}
❌ 在视图中直接访问数据
// BAD
function Dashboard() {
const [data, setData] = useState([]);
useEffect(() => {
fetch('/api/data').then(r => r.json()).then(setData);
}, []);
}
✅ 使用控制器/Hook
// GOOD
function Dashboard() {
const { data, isLoading } = useDashboardData();
}
迁移策略
如果要将现有代码库转换为 MVC:
- 识别各层:将现有代码映射到 M、V 或 C
- 先提取模型:提取数据类型和业务逻辑
- 创建控制器:将现有的数据获取逻辑封装到 Hook/控制器中
- 精简视图:从组件中移除业务逻辑
- 添加测试:为每一层独立编写测试
总结
| 层 | 包含内容 | 依赖项 | 测试方式 |
|---|---|---|---|
| 模型 | 业务逻辑、类型、服务 | 无(纯粹) | 单元测试 |
| 视图 | UI 组件、样式 | 仅 Props | 组件测试 |
| 控制器 | Hook、处理程序、中间件 | 模型、外部 API | 集成测试 |
评论
支持 Markdown