← Назад к вопросам
Как разбить код проекта на модули?
2.0 Middle🔥 151 комментариев
#Soft Skills и рабочие процессы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как разбить код проекта на модули
Это фундаментальный вопрос о архитектуре и организации кода. Модульность напрямую влияет на масштабируемость, тестируемость и поддерживаемость проекта.
1. Принципы модульного дизайна
Single Responsibility Principle (SRP)
Каждый модуль должен иметь одну ответственность:
// Плохо - модуль делает много:
// src/user.js
export function getUser(id) { /* fetch */ }
export function updateUser(id, data) { /* update */ }
export function renderUserProfile(user) { /* render */ }
export function validateEmail(email) { /* validate */ }
// Хорошо - разделено по ответственности:
// src/api/userApi.js
export function getUser(id) { /* fetch */ }
export function updateUser(id, data) { /* update */ }
// src/utils/validators.js
export function validateEmail(email) { /* validate */ }
// src/components/UserProfile.jsx
import { getUser } from '@/api/userApi';
export function UserProfile({ userId }) { /* render */ }
Cohesion (Когезия)
Элементы в модуле должны быть тесно связаны:
// Высокая когезия - связанный код в одном модуле:
// src/features/auth/
// ├── hooks/
// │ └── useAuth.js
// ├── context/
// │ └── AuthContext.jsx
// ├── services/
// │ └── authService.js
// └── types/
// └── auth.types.ts
// Все связано с аутентификацией - это хорошо
2. Структура проекта на примере React
// Рекомендованная структура:
project/
├── src/
│ ├── app/ # Корневой уровень приложения
│ │ ├── App.jsx
│ │ └── Root.jsx # Layout
│ │
│ ├── features/ # Функции (domain-driven)
│ │ ├── auth/
│ │ │ ├── components/
│ │ │ │ └── LoginForm.jsx
│ │ │ ├── hooks/
│ │ │ │ └── useAuth.js
│ │ │ ├── services/
│ │ │ │ └── authService.js
│ │ │ └── types/
│ │ │ └── auth.types.ts
│ │ │
│ │ └── posts/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── types/
│ │
│ ├── shared/ # Переиспользуемые элементы
│ │ ├── ui/ # UI компоненты
│ │ │ ├── Button.jsx
│ │ │ └── Input.jsx
│ │ ├── hooks/ # Утилита хуки
│ │ │ └── useMediaQuery.js
│ │ ├── utils/ # Утилиты
│ │ │ ├── cn.js
│ │ │ └── validators.js
│ │ ├── api/ # API клиент
│ │ │ └── client.js
│ │ └── types/ # Глобальные типы
│ │ └── common.types.ts
│ │
│ ├── pages/ # Page компоненты для routing
│ │ ├── HomePage.jsx
│ │ └── ProfilePage.jsx
│ │
│ ├── main.jsx
│ └── index.css
│
├── public/
├── package.json
└── vite.config.js / webpack.config.js
3. Модульность на практике
Правильное разделение
// src/features/posts/services/postService.js
export async function fetchPosts(page = 1) {
const response = await fetch(`/api/posts?page=${page}`);
return response.json();
}
export async function createPost(data) {
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(data),
});
return response.json();
}
// src/features/posts/hooks/usePosts.js
import { useState, useEffect } from 'react';
import * as postService from '../services/postService';
export function usePosts(page = 1) {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const load = async () => {
try {
const data = await postService.fetchPosts(page);
setPosts(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
load();
}, [page]);
return { posts, loading, error };
}
// src/features/posts/components/PostsList.jsx
import { usePosts } from '../hooks/usePosts';
import { PostItem } from './PostItem';
export function PostsList({ page }) {
const { posts, loading, error } = usePosts(page);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{posts.map(post => (
<PostItem key={post.id} post={post} />
))}
</ul>
);
}
4. Типы модульности
CommonJS (Node.js)
// math.js
function add(a, b) {
return a + b;
}
module.exports = { add };
// main.js
const { add } = require('./math');
console.log(add(2, 3)); // 5
ES Modules (современный стандарт)
// math.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14;
// main.js
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5
// default export
// utils.js
export default function log(msg) {
console.log(msg);
}
// main.js
import log from './utils.js';
log('Hello');
5. Path aliases для чистых импортов
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@features': path.resolve(__dirname, './src/features'),
'@shared': path.resolve(__dirname, './src/shared'),
},
},
});
// tsconfig.json (для TypeScript)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@features/*": ["src/features/*"],
"@shared/*": ["src/shared/*"]
}
}
}
// Использование:
import { usePosts } from '@features/posts/hooks/usePosts';
import { Button } from '@shared/ui/Button';
// Вместо:
// import { usePosts } from '../../../../features/posts/hooks/usePosts';
6. Барьеры между модулями (Index.js pattern)
// src/features/posts/index.js - публичный API
export * from './components/PostsList';
export * from './hooks/usePosts';
export { fetchPosts, createPost } from './services/postService';
export * from './types/post.types';
// Использование:
import { PostsList, usePosts } from '@features/posts';
// Вместо:
// import { PostsList } from '@features/posts/components/PostsList';
// import { usePosts } from '@features/posts/hooks/usePosts';
7. Взаимозависимости между модулями
// Разрешено (стрелка показывает зависимость):
// pages -> features -> shared -> (ничего)
// Правило - зависимости должны идти только в одну сторону:
pages/ProfilePage.jsx
├─ imports -> features/auth
│ ├─ imports -> shared/ui
│ └─ imports -> shared/utils
└─ imports -> shared/types
// Запрещено - циклические зависимости:
// features/posts <-> features/comments
const circularDependency = 'BAD!';
// Нарушение слоев:
// components должны быть in shared, не в features
// services должны быть in features, не в shared
8. Практический пример: функция экспорта и импорта
// src/features/auth/index.ts
export { LoginForm } from './components/LoginForm';
export { useAuth } from './hooks/useAuth';
export type { User, AuthState } from './types/auth.types';
export { authService } from './services/authService';
// src/pages/LoginPage.jsx
import { LoginForm, useAuth } from '@features/auth';
export function LoginPage() {
const { user } = useAuth();
return <LoginForm />;
}
9. Ленивая загрузка модулей
// Разбивка на chunks для оптимизации
import { lazy, Suspense } from 'react';
const PostsList = lazy(() => import('@features/posts/components/PostsList'));
export function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<PostsList />
</Suspense>
);
}
Правила для хорошей модульности
- Одна ответственность - каждый модуль решает одну задачу
- Минимальные зависимости - не импортируй ненужное
- Публичный API - через index.js определи, что экспортируется
- Направленные зависимости - pages -> features -> shared
- Переиспользование - shared модули используются везде
- Группировка по функциям - не по типам файлов
- Тестируемость - каждый модуль легко тестируется отдельно
- Читаемые имена - из названия должна быть ясна цель
Правильная модульность - это инвестиция в будущее проекта. Усложнение в начале окупается простотой масштабирования и поддержки.