← Назад к вопросам
Как организуешь свой код?
1.0 Junior🔥 142 комментариев
#Soft Skills и рабочие процессы
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура и организация кода в React приложении
Организация кода напрямую влияет на масштабируемость, тестируемость и удобство разработки. Я следую принципам Clean Architecture и SOLID при структурировании проектов.
Структура директорий
src/
├── app/ # Next.js App Router или Layout
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ └── (features)/ # Grouped routes
│ ├── questions/
│ │ ├── page.tsx
│ │ └── [id]/
│ │ └── page.tsx
│ └── profile/
│ └── page.tsx
│
├── components/ # React компоненты
│ ├── ui/ # Переиспользуемые компоненты
│ │ ├── Button.tsx
│ │ ├── Card.tsx
│ │ ├── Modal.tsx
│ │ └── Button.test.tsx
│ ├── layout/ # Layout компоненты
│ │ ├── Header.tsx
│ │ ├── Sidebar.tsx
│ │ └── Footer.tsx
│ └── features/ # Feature-specific компоненты
│ ├── QuestionList/
│ │ ├── QuestionList.tsx
│ │ ├── QuestionCard.tsx
│ │ ├── QuestionList.test.tsx
│ │ └── useQuestions.ts
│ └── ProfileCard/
│ ├── ProfileCard.tsx
│ └── ProfileCard.test.tsx
│
├── hooks/ # Кастомные React hooks
│ ├── useAuth.ts
│ ├── useQuestions.ts
│ ├── usePagination.ts
│ └── useMediaQuery.ts
│
├── lib/ # Утилиты и хелперы
│ ├── api.ts # API клиент
│ ├── utils.ts # Общие функции
│ ├── grades.ts # Константы и энумы
│ └── cn.ts # Tailwind className утилита
│
├── contexts/ # React Context
│ ├── AuthContext.tsx
│ └── ThemeContext.tsx
│
├── types/ # TypeScript интерфейсы
│ ├── user.ts
│ ├── question.ts
│ └── api.ts
│
├── constants/ # Глобальные константы
│ ├── api.ts # API endpoints
│ ├── grades.ts # Оценки и категории
│ └── routes.ts # Роуты приложения
│
└── styles/ # Глобальные стили
└── globals.css
Принципы организации
1. Слоистая архитектура
// Presentation Layer (React компоненты)
export function QuestionPage() {
const { questions, loading } = useQuestions(); // Use case
return <QuestionList questions={questions} />;
}
// Application Layer (Hooks, логика)
export function useQuestions() {
const [questions, setQuestions] = useState([]);
const apiClient = useApiClient(); // Dependency injection
useEffect(() => {
apiClient.getQuestions().then(setQuestions);
}, []);
return { questions, loading };
}
// Infrastructure Layer (API клиент, внешние сервисы)
class ApiClient {
async getQuestions(): Promise<Question[]> {
const response = await fetch('/api/v1/questions');
return response.json();
}
}
2. Feature-based структура
Каждая фича содержит все необходимое:
// questions/
// ├── QuestionList.tsx (Компонент)
// ├── QuestionCard.tsx (Подкомпонент)
// ├── useQuestions.ts (Hook логика)
// ├── questionService.ts (Business logic)
// └── QuestionList.test.tsx (Тесты)
// Преимущества:
// - Когда нужно удалить фичу - удаляем одну папку
// - Все связанное в одном месте
// - Легко переиспользовать в других проектах
3. Разделение логики
// Компонент отвечает за UI
interface QuestionListProps {
questions: Question[];
onSelect: (q: Question) => void;
}
export function QuestionList({ questions, onSelect }: QuestionListProps) {
return (
<ul>
{questions.map(q => (
<li key={q.id} onClick={() => onSelect(q)}>
{q.title}
</li>
))}
</ul>
);
}
// Hook отвечает за логику
export function useQuestions() {
const [questions, setQuestions] = useState<Question[]>([]);
const [loading, setLoading] = useState(false);
const api = useApi();
useEffect(() => {
setLoading(true);
api.getQuestions()
.then(setQuestions)
.finally(() => setLoading(false));
}, [api]);
return { questions, loading };
}
// Использование
function QuestionPage() {
const { questions, loading } = useQuestions();
if (loading) return <Spinner />;
return <QuestionList questions={questions} onSelect={console.log} />;
}
Типизация
// types/question.ts - единый источник истины
export interface Question {
id: string;
title: string;
description: string;
difficulty: 'easy' | 'medium' | 'hard';
profession_id: string;
}
export interface QuestionResponse {
data: Question[];
total: number;
page: number;
}
// Использование везде
const response: QuestionResponse = await api.get('/questions');
Тестирование
// useQuestions.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { useQuestions } from './useQuestions';
describe('useQuestions', () => {
it('should load questions on mount', async () => {
const { result } = renderHook(() => useQuestions());
expect(result.current.loading).toBe(true);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.questions).toHaveLength(3);
});
});
// QuestionList.test.tsx
import { render, screen } from '@testing-library/react';
import { QuestionList } from './QuestionList';
describe('QuestionList', () => {
it('should render list of questions', () => {
const questions = [
{ id: '1', title: 'Q1', difficulty: 'easy' }
];
render(<QuestionList questions={questions} onSelect={vi.fn()} />);
expect(screen.getByText('Q1')).toBeInTheDocument();
});
});
Константы и конфигурация
// constants/api.ts
export const API_ROUTES = {
questions: '/api/v1/questions',
users: '/api/v1/users',
professions: '/api/v1/professions',
} as const;
export const REVALIDATE_TIME = {
questions: 60, // 1 минута
users: 3600, // 1 час
static: 86400, // 1 день
} as const;
// Использование
const response = fetch(API_ROUTES.questions, {
next: { revalidate: REVALIDATE_TIME.questions }
});
Best Practices которые я следую
- DRY - не повторяю код, выношу в функции/компоненты
- SOLID принципы - каждый класс/компонент имеет одну ответственность
- Типизация - TypeScript strict mode везде
- Тестирование - минимум 90% coverage
- Документация - через JSDoc комментарии
- Именование - ясные, описательные имена
- Ленивая загрузка - динамический импорт для больших компонентов
- Кеширование - умное использование React.memo и useMemo
Этот подход позволяет писать масштабируемый, тестируемый и поддерживаемый код.