Собираем React-приложение из компонентов
Содержание
- Введение: почему композиция, а не наследование
- Что такое композиция и наследование
- 1 способ композиции:
props.children - 2 способ композиции: специальные пропсы (не
children) - 3 способ композиции: смешивание
childrenиprops - Реальный пример: компонент с настройками
- Когда использовать композицию (основные паттерны)
- Композиция как альтернатива наследованию
- Специализация через композицию
- Контейнеры и презентационные компоненты
- Композиция высшего порядка (HOC vs композиция)
- Сравнение с наследованием (для понимания)
- Частые ошибки новичков
- Итог (что мы узнали)
- Задание для закрепления (на 10–12 минут)
- Что дальше?
Цель урока: понять главный принцип построения React-приложений: композиция (сборка из мелких компонентов) вместо наследования (создания иерархий классов). Научиться создавать гибкие, переиспользуемые компоненты через передачу детей (children) и пропсов-компонентов.
Введение: почему композиция, а не наследование
Главная мысль:
React построен на композиции, а не на наследовании. Ты не создаёшь «родительский компонент» и не наследуешь от него. Вместо этого ты собираешь приложение из маленьких независимых кусочков, как из деталей конструктора.
Почему композиция лучше?
- Гибкость — можно менять части независимо
- Переиспользование — один компонент можно вставить в разные места
- Простота — каждый компонент делает одну вещь
- Тестируемость — легко тестировать маленькие части
Что такое композиция и наследование
| Подход | Что это | Пример в React |
|---|---|---|
| Наследование | Класс получает свойства и методы от другого класса | ❌ Не используется в React |
| Композиция | Компонент собирается из других компонентов | ✅ Основной подход в React |
Наследование (не в React):
// Это НЕ React-способ
class Animal {
eat() { ... }
}
class Dog extends Animal {
bark() { ... }
}
Композиция (в React):
function App() {
return (
<Page>
<Header />
<Content>
<Article />
</Content>
<Footer />
</Page>
);
}
1 способ композиции: props.children
Базовый пример:
function Card({ children }) {
return (
<div className="card">
<div className="card-content">
{children}
</div>
</div>
);
}
function App() {
return (
<Card>
<h2>Заголовок</h2>
<p>Текст внутри карточки</p>
</Card>
);
}
Что здесь происходит:
Card— компонент-обёрткаchildren— всё, что находится между открывающим и закрывающим тегом<Card>Appрешает, какое содержимое будет внутри карточки
Более сложный пример:
function Layout({ children }) {
return (
<div className="layout">
<header>Шапка сайта</header>
<main>{children}</main>
<footer>Подвал сайта</footer>
</div>
);
}
function App() {
return (
<Layout>
<h1>Добро пожаловать!</h1>
<p>Это содержимое страницы</p>
</Layout>
);
}
2 способ композиции: специальные пропсы (не children)
Иногда нужно передать несколько «слотов» в компонент:
Пример с несколькими вставками:
function SplitPanel({ left, right }) {
return (
<div className="split-panel">
<div className="left">{left}</div>
<div className="right">{right}</div>
</div>
);
}
function App() {
return (
<SplitPanel
left={<Sidebar />}
right={<MainContent />}
/>
);
}
Что здесь происходит:
SplitPanelожидает два пропса:leftиright- Они могут содержать любые компоненты
SplitPanelрешает, как их расположить
Оживи свой сайт. Освой JavaScript!
Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.
От теории — к реальным скриптам в твоём портфолио.
3 способ композиции: смешивание children и props
function Dialog({ title, actions, children }) {
return (
<div className="dialog">
<h2>{title}</h2>
<div className="dialog-body">{children}</div>
<div className="dialog-actions">{actions}</div>
</div>
);
}
function App() {
return (
<Dialog
title="Подтверждение"
actions={
<>
<button>Отмена</button>
<button>Подтвердить</button>
</>
}
>
<p>Вы уверены, что хотите удалить этот файл?</p>
</Dialog>
);
}
Реальный пример: компонент с настройками
// Базовый компонент-контейнер
function Card({ title, icon, children, footer, onClose }) {
return (
<div className="card">
<div className="card-header">
{icon && <span className="icon">{icon}</span>}
<h3>{title}</h3>
{onClose && <button onClick={onClose}>✕</button>}
</div>
<div className="card-body">
{children}
</div>
{footer && (
<div className="card-footer">
{footer}
</div>
)}
</div>
);
}
// Использование
function App() {
return (
<Card
title="Профиль пользователя"
icon={<img src="/avatar.png" alt="Avatar" />}
footer={<button>Сохранить</button>}
onClose={() => console.log('Закрыто')}
>
<p>Имя: Алексей</p>
<p>Email: alex@example.com</p>
</Card>
);
}
Когда использовать композицию (основные паттерны)
| Паттерн | Описание | Когда использовать |
|---|---|---|
| Контейнер | Компонент, который содержит другие компоненты | Для страниц, секций, модальных окон |
| Специализация | Создание конкретных версий общего компонента | Для вариаций (например, кнопки разных цветов) |
| Конфигурация | Передача конфигурации через пропсы | Для настройки поведения и внешнего вида |
Композиция как альтернатива наследованию
Проблема: хочется создать разные типы кнопок.
Наследование (плохо в React):
// ❌ Не делайте так
class Button extends React.Component { ... }
class PrimaryButton extends Button { ... }
class DangerButton extends Button { ... }
Композиция (правильный путь):
// ✅ Делайте так
function Button({ variant, children, ...props }) {
const style = {
primary: { color: 'white', background: 'blue' },
danger: { color: 'white', background: 'red' },
outline: { color: 'blue', background: 'transparent' }
};
return (
<button style={style[variant]} {...props}>
{children}
</button>
);
}
// Использование
function App() {
return (
<>
<Button variant="primary">Сохранить</Button>
<Button variant="danger">Удалить</Button>
<Button variant="outline">Отмена</Button>
</>
);
}
Специализация через композицию
Создание конкретных компонентов из общего:
// Общий компонент
function UserCard({ user, extraInfo, children }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>Возраст: {user.age}</p>
{extraInfo && <p>{extraInfo}</p>}
{children}
</div>
);
}
// Специализированные версии
function SimpleUserCard({ user }) {
return <UserCard user={user} />;
}
function DetailedUserCard({ user }) {
return (
<UserCard
user={user}
extraInfo={`Город: ${user.city}, Работа: ${user.job}`}
>
<button>Посмотреть профиль</button>
</UserCard>
);
}
function DeletableUserCard({ user, onDelete }) {
return (
<UserCard user={user}>
<button onClick={onDelete}>Удалить пользователя</button>
</UserCard>
);
}
Контейнеры и презентационные компоненты
Презентационный компонент — отвечает только за отображение:
function UserProfile({ user, onEdit }) {
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<button onClick={onEdit}>Редактировать</button>
</div>
);
}
Контейнер — управляет данными и логикой:
function UserProfileContainer() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser().then(data => {
setUser(data);
setLoading(false);
});
}, []);
const handleEdit = () => {
// Логика редактирования
};
if (loading) return <p>Загрузка...</p>;
if (!user) return <p>Пользователь не найден</p>;
return <UserProfile user={user} onEdit={handleEdit} />;
}
Преимущества:
- Презентационный компонент легко переиспользовать
- Контейнер содержит только логику
- Легко тестировать оба
Композиция высшего порядка (HOC vs композиция)
В React существует HOC (Higher Order Component) — но это тоже композиция:
// HOC — компонент, который принимает компонент и возвращает новый
function withLoading(Component) {
return function WrappedComponent({ isLoading, ...props }) {
if (isLoading) {
return <p>Загрузка...</p>;
}
return <Component {...props} />;
};
}
// Использование
const UserListWithLoading = withLoading(UserList);
const ProductListWithLoading = withLoading(ProductList);
Важно: HOC — это тоже композиция, а не наследование!
Сравнение с наследованием (для понимания)
| Аспект | Наследование (не React) | Композиция (React) |
|---|---|---|
| Отношение | «Является» (is-a) | «Имеет» (has-a) |
| Гибкость | Ограничена иерархией | Высокая, можно комбинировать |
| Переиспользование | Через родительский класс | Через пропсы и children |
| Сложность | Увеличивается с глубиной | Растёт линейно |
| Тестирование | Трудно из-за зависимости | Просто — компоненты изолированы |
Частые ошибки новичков
| Ошибка | Почему | Исправление |
|---|---|---|
Создают иерархии компонентов через extends |
React не поддерживает наследование | Используй композицию |
Не используют children |
Дублируют код | Передавай children |
| Хардкодят зависимости | Компонент становится жёстким | Передавай всё через пропсы |
| Создают сложные контейнеры | Смешивают логику и отображение | Разделяй на контейнер и презентацию |
Итог (что мы узнали)
| Что узнали | Как это работает |
|---|---|
| Композиция | Сборка компонентов из других компонентов |
| Наследование | ❌ Не используется в React |
children |
Базовый способ композиции |
| Пропсы-компоненты | Передача компонентов через пропсы |
| Специализация | Создание конкретных версий через пропсы |
| Контейнеры vs Презентация | Разделение логики и отображения |
| HOC | Композиция через обёртку |
Задание для закрепления (на 10–12 минут)
Создай систему из трёх компонентов:
Компонент Modal (контейнер) с пропсами
title— заголовок модалкиchildren— содержимоеactions— кнопки действий (передаются как пропс)onClose— функция закрытия
Компонент ConfirmationModal (специализация) — модалка для подтверждения
- Использует
Modalвнутри - Принимает
messageиonConfirm - Автоматически добавляет кнопки «Отмена» и «Подтвердить»
Компонент FormModal (специализация) — модалка с формой:
- Использует
Modalвнутри - Принимает
fields(массив полей формы) - Автоматически добавляет кнопку «Отправить»
Проверка: В App.jsx отобрази обе модалки и убедись, что они работают.
Что дальше?
В следующем уроке разберём Основные хуки — useState, useEffect, useMemo, useReducer, useCallback, useRef, useContext. Ты познакомишься со всеми базовыми инструментами для управления состоянием, производительностью и побочными эффектами. Это ключевая тема для понимания современных React-приложений.