Component Architecture

Зачем нужно

Component Architecture — набор принципов организации компонентов в проекте. Когда в приложении 200+ компонентов, вопрос «где что лежит» становится критичным. Хорошая архитектура делает код предсказуемым: любой разработчик знает, где искать компонент, куда добавить новый и какие зависимости допустимы.

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

  • Крупные SPA-проекты (50+ компонентов)
  • UI-библиотеки и дизайн-системы
  • Командная разработка (единые правила структуры)
  • Любой фреймворк (React, Vue, Angular, Svelte)

Atomic Design

Методология Брэда Фроста — организация компонентов по уровням абстракции:

Atoms → Molecules → Organisms → Templates → Pages

Атомы:     Button, Input, Label, Icon, Avatar
Молекулы:  SearchBar (Input + Button), FormField (Label + Input)
Организмы: Header (Logo + Nav + SearchBar), ProductCard
Шаблоны:   PageLayout (Header + Sidebar + Content)
Страницы:  HomePage, CatalogPage (шаблон + данные)

Структура проекта

src/
├── components/
│   ├── atoms/
│   │   ├── Button/
│   │   │   ├── Button.js
│   │   │   ├── Button.module.css
│   │   │   ├── Button.test.js
│   │   │   └── index.js          ← barrel export
│   │   ├── Input/
│   │   └── Icon/
│   ├── molecules/
│   │   ├── SearchBar/
│   │   ├── FormField/
│   │   └── NavLink/
│   ├── organisms/
│   │   ├── Header/
│   │   ├── ProductCard/
│   │   └── Sidebar/
│   ├── templates/
│   │   ├── MainLayout/
│   │   └── DashboardLayout/
│   └── pages/
│       ├── HomePage/
│       └── CatalogPage/

Пример компонентов по уровням

// === Atom: Button ===
function Button({ text, onClick, variant = 'primary', disabled = false }) {
  return `
    <button
      class="btn btn--${variant}"
      ${disabled ? 'disabled' : ''}
    >${text}</button>
  `;
}

// === Molecule: SearchBar = Input + Button ===
function SearchBar({ onSearch }) {
  return `
    <div class="search-bar">
      ${Input({ placeholder: 'Поиск...', id: 'search' })}
      ${Button({ text: 'Найти', onClick: onSearch })}
    </div>
  `;
}

// === Organism: Header = Logo + Nav + SearchBar ===
function Header({ user, onSearch }) {
  return `
    <header class="header">
      ${Logo}
      ${Nav({ items: ['Главная', 'Каталог', 'О нас'] })}
      ${SearchBar({ onSearch })}
      ${user ? Avatar({ name: user.name }) : Button({ text: 'Войти' })}
    </header>
  `;
}

// === Template: MainLayout ===
function MainLayout({ header, sidebar, content, footer }) {
  return `
    <div class="layout">
      <div class="layout__header">${header}</div>
      <div class="layout__body">
        <aside class="layout__sidebar">${sidebar}</aside>
        <main class="layout__content">${content}</main>
      </div>
      <div class="layout__footer">${footer}</div>
    </div>
  `;
}

// === Page: HomePage — шаблон + данные ===
function HomePage() {
  const products = fetchProducts;
  return MainLayout({
    header: Header({ user: currentUser }),
    sidebar: Sidebar({ categories }),
    content: ProductGrid({ products }),
    footer: Footer,
  });
}

Feature-Sliced Design (FSD)

Российская методология, популярная в СНГ. Организация по слоям и фичам:

src/
├── app/              ← Инициализация приложения
│   ├── index.js
│   ├── providers/    ← Глобальные провайдеры (Router, Store, Theme)
│   └── styles/
│
├── pages/            ← Страницы (композиция фич)
│   ├── home/
│   ├── catalog/
│   └── profile/
│
├── widgets/          ← Крупные самостоятельные блоки
│   ├── header/
│   ├── sidebar/
│   └── product-list/
│
├── features/         ← Пользовательские сценарии
│   ├── auth/
│   │   ├── ui/       ← Компоненты
│   │   ├── model/    ← Логика, store
│   │   ├── api/      ← Запросы
│   │   └── index.js  ← Public API
│   ├── add-to-cart/
│   └── search/
│
├── entities/         ← Бизнес-сущности
│   ├── user/
│   │   ├── ui/       ← UserCard, UserAvatar
│   │   ├── model/    ← userStore, types
│   │   ├── api/      ← getUser, updateUser
│   │   └── index.js
│   ├── product/
│   └── order/
│
└── shared/           ← Переиспользуемое (без бизнес-логики)
    ├── ui/           ← Button, Input, Modal
    ├── lib/          ← Утилиты, хелперы
    ├── api/          ← API-клиент, interceptors
    └── config/       ← Константы, env

Правила зависимостей FSD

app → pages → widgets → features → entities → shared
 ↓       ↓       ↓         ↓          ↓         ↓
Может импортировать только из слоёв НИЖЕ

✅ features/auth → entities/user → shared/ui
✅ pages/home → widgets/header → features/auth
❌ entities/user → features/auth (нельзя вверх!)
❌ shared/ui → entities/product  (нельзя вверх!)
// features/add-to-cart/ui/AddToCartButton.js
import { Button } from '@/shared/ui';           // ✅ shared
import { Product } from '@/entities/product';    // ✅ entities
// import { Auth } from '@/features/auth';       // ❌ features→features

export function AddToCartButton({ product }) {
  const handleClick = () => {
    cartModel.add(product);
  };

  return Button({
    text: `В корзину — ${product.price} ₽`,
    onClick: handleClick,
  });
}

Barrel Exports

Каждый модуль экспортирует публичный API через index.js:

// components/Button/index.js
export { Button } from './Button';
export { ButtonGroup } from './ButtonGroup';
// НЕ экспортируем внутренние детали
// НЕ export { getButtonStyles } from './styles';

// Использование — чистый import
import { Button, ButtonGroup } from '@/components/Button';
// Вместо:
// import { Button } from '@/components/Button/Button';
// shared/ui/index.js — barrel для всего UI-слоя
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';
export { Spinner } from './Spinner';

// Одна строка импорта
import { Button, Input, Modal } from '@/shared/ui';

Осторожно: Tree Shaking

// ❌ Проблема: barrel re-exports могут сломать tree shaking
// Если бандлер не может удалить неиспользуемые экспорты

// ✅ Решение: sideEffects в package.json
{
  "sideEffects": false
}

// Или прямые импорты для критичных случаев:
import { Button } from '@/shared/ui/Button';

Организация файлов компонента

// Вариант 1: Папка на компонент (рекомендуется)
Button/
├── Button.js          ← Компонент
├── Button.module.css  ← Стили
├── Button.test.js     ← Тесты
├── Button.stories.js  ← Storybook
├── useButton.js       ← Хук (если есть)
└── index.js           ← Re-export

// Вариант 2: Файлы по типу (для маленьких проектов)
components/
├── Button.js
├── Button.css
├── Input.js
├── Input.css

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

  1. Нет архитектуры — 200 компонентов в одной папке components/
  2. Circular dependencies — компоненты импортируют друг друга по кругу
  3. Нарушение направления зависимостей — shared импортирует из features
  4. God Component — один компонент на 1000 строк вместо декомпозиции
  5. Перфекционизм — маленький проект на 10 компонентов не нуждается в FSD
  6. Barrel bloat — barrel файлы с 100 экспортами замедляют IDE

Практика

  1. Разложить UI-макет на Atoms → Molecules → Organisms
  2. Организовать структуру проекта по Atomic Design
  3. Создать barrel exports для каждого модуля
  4. Попробовать Feature-Sliced Design на TODO-приложении
  5. Проверить: нет ли циклических зависимостей (madge --circular)

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

Ресурсы