Модуль 15

Композиція компонентів

Ось готовий урок, згенерований у стилі Девіда Малана (CS50) спеціально для тебе. Ми розберемо Композицію компонентів — тему, яка перетворює хаос коду на елегантну архітектуру.


🎓 Урок: Композиція компонентів

(Або як перестати будувати будинки з піску і почати будувати з LEGO)

👋 Привіт, друзі! Радий бачити вас тут.

Сьогодні ми поговоримо про дещо фундаментальне. Дещо, що відрізняє новачка, який пише "спагеті-код", від інженера, який будує системи рівня Facebook чи Netflix. Це — Композиція.


1. 🔥 Вступ: Проблема та мотивація

Уявіть собі ситуацію. Ви вирішили побудувати власний інтернет-магазин. Ви відкриваєте редактор коду і починаєте писати HTML. Спочатку ви робите картку товару для iPhone: картинка, заголовок, ціна, кнопка "Купити". Чудово!

Але потім вам потрібно додати ще 50 товарів. Що ви робите? 1. Копіюєте код картки 50 разів? 2. А якщо завтра дизайнер скаже: "Слухай, давай зробимо всі кнопки "Купити" не синіми, а зеленими і круглими?"

Вам доведеться лізти в 50 різних місць і змінювати код вручну? Це пекло. Це шлях до помилок.

А тепер уявіть LEGO. Коли ви будуєте замок з LEGO, ви ж не відливаєте кожну стіну з рідкого пластику щоразу заново, правда? Ви берете готові "цеглинки". Ви берете "вікно", берете "двері" і вставляєте їх у "стіну".

Ось у чому суть: У програмуванні ми хочемо того ж самого. Ми хочемо створити компонент "Кнопка" один раз і використовувати його скрізь. Ми хочемо вкладати компоненти один в одного, як матрьошки.

Питання до вас: Якби ви будували автомобіль, ви б хотіли винаходити двигун заново для кожної моделі, чи просто взяти готовий двигун і вставити його в новий корпус?

Сьогодні ми навчимося "вставляти двигуни".


2. 🧠 Теоретична база (без нудьги)

Що ж таке Композиція компонентів?

Простими словами: це процес створення складних компонентів шляхом об'єднання простіших. Це принцип "Розділяй і володарюй".

Як це працює "під капотом"?

Уявіть дерево. У нас є корінь (весь додаток), гілки (великі розділи: хедер, сайдбар, контент) і листя (кнопки, іконки, текст).

Коли ми робимо композицію, ми кажемо Батьківському компоненту (Parent): "Гей, у тебе всередині буде ось цей Дочірній компонент (Child)".

🔑 Два типи композиції, які треба зрозуміти:

  1. Containment (Вкладення / Контейнери): Це як коробка. Коробці байдуже, що в ній лежить — піца, взуття чи кіт. У коді це часто реалізується через спеціальну пропсу (в React це children, у Vue — slots). Приклад: Модальне вікно (Modal), яке просто показує те, що ви в нього передали.

  2. Specialization (Спеціалізація): Це коли ми беремо загальний компонент і робимо його "особливу" версію. Приклад: Є звичайна Button. Ми створюємо DeleteButton, яка використовує всередині Button, але завжди червона і має іконку смітника.

❗️ Що запам'ятати залізно: Компоненти не повинні знати надто багато про своїх "дітей". Чим менше вони знають, тим вони універсальніші.


3. 🧪 Приклади (від простого до реального)

Давайте подивимось на код (на прикладі React/JSX, бо це стандарт індустрії, але логіка однакова скрізь).

Рівень 1: Мінімальний приклад (Спеціалізація)

У нас є проста кнопка.

// Базовий компонент
function Button({ color, text }) {
  return <button style={{ backgroundColor: color }}>{text}</button>;
}

// Композиція: ми створюємо "Небезпечну Кнопку" з "Звичайної Кнопки"
function DangerButton() {
  return <Button color="red" text="Видалити все!" />;
}

Питання: Що буде, якщо ми змінимо розмір шрифту в компоненті Button? Відповідь: Він автоматично зміниться і в DangerButton. Магія! ✨


Рівень 2: Реальний приклад (Вкладення / Children)

Тепер цікавіше. Уявіть, що ми робимо красиву рамку для фото або тексту.

Що ви очікуєте? Ми хочемо написати компонент-обгортку, в який можна запхати будь-що.

// FancyBorder не знає, що буде всередині. Він просто малює рамку.
function FancyBorder(props) {
  return (
    <div className={'fancy-border fancy-border-' + props.color}>
      {/* Ось тут магія! props.children — це те, що ми вкладемо всередину */}
      {props.children}
    </div>
  );
}

// Використання:
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      {/* Ми передаємо заголовок і параграф ЯК ДІТЕЙ */}
      <h1 className="Dialog-title">Ласкаво просимо!</h1>
      <p className="Dialog-message">Дякуємо, що завітали на наш урок.</p>
    </FancyBorder>
  );
}

Чому це круто? FancyBorder відповідає тільки за рамку. Йому байдуже, чи там текст, чи картинка, чи інша форма.


Рівень 3: Складніша композиція (Слот-патерн)

Іноді нам треба покласти щось "наліво", а щось "направо".

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left} {/* Слот для лівої частини */}
      </div>
      <div className="SplitPane-right">
        {props.right} {/* Слот для правої частини */}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={<Contacts />}
      right={<Chat />}
    />
  );
}

Ми зібрали макет сторінки, просто передавши компоненти як пропси!


4. 🛠 Практична частина

Час забруднити руки кодом! Відкрийте свій редактор (або CodeSandbox).

Завдання 1: Розігрів Скопіюйте приклад Button та DangerButton. Створіть третій компонент SuccessButton (зелений, текст "Готово"), використовуючи той самий базовий Button.

Завдання 2: "Конструктор LEGO" Створіть компонент Card (картка). Він має приймати children. * Додайте стилі до Card (тінь, заокруглені кути, відступи). * Використайте Card тричі: 1. Для відображення тексту. 2. Для відображення картинки. 3. Для відображення іншої Card всередині (Картка в картці).

Завдання 3: Виправлення помилки У вас є код:

function UserLayout() {
  return (
    <Navbar />
    <Sidebar />
    <Feed />
  );
}

Це не спрацює (принаймні в React). Чому? Як виправити це, використовуючи композицію або фрагменти?

Завдання 4: Міні-кейс "Сайт новин" Створіть структуру сторінки, використовуючи компоненти: * Header (містить Логотип і Меню) * Layout (приймає children) * Article (приймає заголовок і текст) Зберіть це разом так, щоб у App ви виглядали приблизно так: <Layout><Header /><Article ... /></Layout>

Завдання 5: А що, якщо... Спробуйте передати в компонент Button не просто текст, а іконку (emoji) разом з текстом через children, а не через проп text. Перепишіть компонент Button, щоб він підтримував це.


5. 💡 Мислення як у розробника

Як відрізнити гарну композицію від поганої?

🚫 Типова помилка новачка: "God Component" (Божественний об'єкт)

Новачки часто створюють один компонент UserProfile, де пишуть 500 рядків коду: і верстку аватара, і логіку кнопки, і форматування адреси. Не робіть так. Якщо ви бачите, що компонент займає більше 100-150 рядків — це сигнал тривоги. Розбивайте його!

🚫 Помилка: Prop Drilling (Буріння пропсів)

Це коли ви передаєте дані через 10 компонентів вниз, хоча проміжні компоненти ці дані не використовують. Рішення: Використовуйте композицію (вкладення), щоб передати компонент відразу туди, де він потрібен.

🧠 Як думає Senior:

"Я бачу цю кнопку в дизайні вже втретє. Я не буду її копіювати. Я винесу її в окремий модуль UI/Button. А цей складний віджет я розіб'ю на Header, Body і Footer, щоб потім можна було легко змінити Header, не ламаючи Footer."

Золоте правило: Компонент має робити одну річ, але робити її добре. (Single Responsibility Principle).


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? Ми відмовилися від монолітного, страшного коду. Ми навчилися: 1. Створювати будівельні блоки (компоненти). 2. Вкладати їх один в одного (children/slots). 3. Спеціалізувати їх (робити унікальні версії загальних речей).

Тепер ви можете будувати складні інтерфейси, зберігаючи код чистим і зрозумілим. Ви будуєте своє місто з LEGO, а не ліпите його з бруду.

🤔 Тизер наступного уроку:

Добре, у нас є красиві компоненти. Але вони "німі". Кнопка натискається, але нічого не відбувається. Як змусити їх "говорити" один з одним? Як передати сигнал від кнопки "Купити" до іконки "Кошик"?

На наступному уроці ми поговоримо про State (Стан) та Потік Даних. Це буде 🔥.

А поки що — практикуйтесь! Код сам себе не напише. Успіхів!