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
Частые ошибки
- Нет архитектуры — 200 компонентов в одной папке
components/ - Circular dependencies — компоненты импортируют друг друга по кругу
- Нарушение направления зависимостей — shared импортирует из features
- God Component — один компонент на 1000 строк вместо декомпозиции
- Перфекционизм — маленький проект на 10 компонентов не нуждается в FSD
- Barrel bloat — barrel файлы с 100 экспортами замедляют IDE
Практика
- Разложить UI-макет на Atoms → Molecules → Organisms
- Организовать структуру проекта по Atomic Design
- Создать barrel exports для каждого модуля
- Попробовать Feature-Sliced Design на TODO-приложении
- Проверить: нет ли циклических зависимостей (
madge --circular)
Связанные темы
- Компонентный подход — основы компонентов
- MVC — классическая альтернатива
- Управление состоянием — state management в контексте архитектуры
- Webpack — tree shaking и barrel exports