Архитектура фронтенд-приложения

Архитектура фронтенд-приложения — набор принципов и паттернов организации кода (разделение ответственности, управление состоянием, структура модулей), определяющих как приложение масштабируется и поддерживается.

Зачем нужно

  • Без явной архитектуры код быстро превращается в «спагетти» — компоненты знают друг о друге, бизнес-логика размазана по UI
  • Правильная структура позволяет команде работать параллельно, не конфликтуя в одних и тех же файлах
  • Архитектурные решения напрямую влияют на тестируемость, производительность и скорость разработки фич

Где используется

  • SPA на React, Vue, Angular — выбор между Feature-Sliced Design, слоистой архитектурой, модульной
  • Большие монолиты — разбивка на домены/features
  • Микрофронтенды — независимые команды с отдельными репозиториями и деплоем
  • Next.js / Nuxt — серверный рендеринг требует учёта server/client разделения

Основной контент

Эволюция архитектур: MVC → Flux → современность

MVC (традиционный):
  Model ←→ Controller ←→ View
  Проблема: двунаправленный поток данных, сложно отследить

Flux (Facebook, 2014):
  Action → Dispatcher → Store → View → Action
  Однонаправленный поток — предсказуемость

Redux (2015, Flux-производный):
  Action → Reducer → Store → React Component → Action
  Иммутабельное состояние, чистые функции

Современный React (2022+):
  Server Components + Client Components + RSC
  Состояние: Zustand / Jotai / TanStack Query

Слоистая архитектура фронтенда

┌─────────────────────────────┐
│  UI Layer (компоненты)       │ — рендеринг, пользовательские события
├─────────────────────────────┤
│  Feature Layer               │ — бизнес-логика фичи, use cases
├─────────────────────────────┤
│  Domain Layer                │ — модели данных, типы, валидация
├─────────────────────────────┤
│  Infrastructure Layer        │ — HTTP-клиент, localStorage, WebSocket
└─────────────────────────────┘

Feature-Sliced Design (FSD) — структура папок

src/
  app/         — инициализация, провайдеры, роутер
  pages/       — страницы (композиция фич)
  widgets/     — самостоятельные блоки UI
  features/    — пользовательские действия (login, add-to-cart)
  entities/    — бизнес-сущности (user, product, order)
  shared/      — переиспользуемый код без бизнес-логики
    ui/        — базовые компоненты (Button, Input)
    api/       — HTTP-клиент
    lib/       — утилиты
    config/    — константы, env

Управление состоянием

Локальное состояние (useState, useReducer):
  Данные живут в одном компоненте или поддереве

Серверное состояние (TanStack Query, SWR):
  Кэш ответов API, синхронизация с сервером, loading/error

Глобальное UI-состояние (Zustand, Jotai):
  Тема, модальные окна, флаги — без серверных данных

Сложная бизнес-логика (Redux Toolkit):
  Строгий unidirectional flow, DevTools, time-travel debugging

Пример разделения ответственности на TypeScript

// entities/user/model.ts — доменная модель
export interface User {
  id: number;
  name: string;
  email: string;
  role: "admin" | "user";
}

// shared/api/http-client.ts — инфраструктура
export const httpClient = {
  async get<T>(url: string): Promise<T> {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json() as Promise<T>;
  },
};

// features/auth/api.ts — feature API
import { httpClient } from "@/shared/api/http-client";
import type { User } from "@/entities/user";

export async function fetchCurrentUser: Promise<User> {
  return httpClient.get<User>("/api/me");
}

// features/auth/model.ts — feature state
import { create } from "zustand";

interface AuthState {
  user: User | null;
  setUser(user: User | null): void;
}

export const useAuthStore = create<AuthState>(set => ({
  user: null,
  setUser: user => set({ user }),
}));

Принципы чистой архитектуры на фронтенде

  • Dependency Rule — зависимости направлены только внутрь (UI → Feature → Domain, но не наоборот)
  • Single Responsibility — каждый модуль отвечает за одно
  • Interface Segregation — компоненты зависят от минимального интерфейса (пропсы — только то, что нужно)
  • Инверсия зависимостей — бизнес-логика не импортирует конкретный HTTP-клиент напрямую

Частые ошибки

  • Вся логика в компонентах — fetch, обработка, форматирование — всё в одном компоненте; невозможно тестировать и переиспользовать
  • Глобальный стор для всего — серверные данные в Redux вместо TanStack Query приводят к сложной синхронизации
  • Circular dependencies между слоями — Feature импортирует из другой Feature напрямую; решение — вынести общее в Shared
  • Преждевременная архитектура — FSD для проекта на 3 страницы излишне; начинать просто и рефакторить по необходимости
  • Отсутствие явных границ модулей — импорты из глубины чужих модулей (../../features/auth/internal/store) нарушают инкапсуляцию

Связанные темы

Ресурсы