Модуль 21

Керування глобальним станом: Context API

Ось готовий урок, створений за твоїм майстер-промптом.


🏛 CS50-Style: Керування глобальним станом (Context API)

Вітаю, друзі! 👋

Сьогодні ми поговоримо про одну з найболючіших проблем, з якою стикається кожен React-розробник, коли його застосунок виростає з розмірів "Hello World" до чогось реального.

1. 🔥 Вступ: Пекло прокидування (Prop Drilling)

Уявіть собі ситуацію. Ви будуєте величезний хмарочос — ваш додаток. На самому даху (у кореневому компоненті App) у вас є генератор електрики (ваші дані, наприклад, інформація про залогіненого користувача).

А тепер уявіть, що у підвалі будівлі є маленька лампочка (компонент Avatar або ProfileButton), яку треба заживити від цього генератора.

Як ви це робите зараз? Ви тягнете кабель з даху на 100-й поверх. Там передаєте його в руки електрику на 99-му. Той — на 98-й. І так далі, через сотню поверхів (компонентів), які навіть не використовують цю електрику, вони просто передають кабель далі вниз.

У React це називається Prop Drilling (свердління пропсів).

Запитайте себе:

Чи має компонент Sidebar знати про userEmail, якщо він просто містить в собі компонент Header, який містить UserMenu, який містить Profile?

Звісно, ні! Це створює "спагеті-код". Якщо ви зміните назву пропса нагорі, вам доведеться бігати по всіх "поверхах" і переписувати код. Це неефективно. Це втомлює.

Рішення? Нам потрібен спосіб телепортувати дані з даху прямо у підвал, оминаючи все посередині. У звичайному житті це радіохвилі. У React — це Context API.


2. 🧠 Теоретична база (Що під капотом?)

Context API — це вбудований у React механізм, який дозволяє "транслювати" дані.

Уявіть це як систему Wi-Fi у вашому будинку: 1. Router (Роутер) — це джерело сигналу. 2. Signal (Сигнал) — це дані (пароль, тема, користувач). 3. Devices (Пристрої) — це компоненти, які "підключаються" до Wi-Fi, щоб отримати дані.

Три кити Context API:

  1. createContext()
    • Це створення самої "мережі". Ми кажемо React: "Гей, ми хочемо мати канал для передачі теми (світла/темна)".
  2. Provider (Провайдер/Постачальник)
    • Це компонент-обгортка (наш Роутер). Він каже: "Все, що знаходиться всередині мене, може отримати доступ до цих даних".
    • Головне правило: Провайдер має бути вище у дереві компонентів, ніж ті, хто хоче читати дані.
  3. useContext (Хук-споживач)
    • Це "антена" у вашому компоненті. Ви просто пишете const data = useContext(MyContext) і магічним чином отримуєте значення з Провайдера, ігноруючи всіх посередників.

Інтуїтивно: Context — це глобальна змінна для певної частини вашого дерева компонентів, яка автоматично оновлює всіх, хто її слухає, коли дані змінюються.


3. 🧪 Приклади: Від темряви до світла

Давайте розберемо класичний приклад — перемикання теми (Темна/Світла).

Крок 1: Створення контексту

Створимо окремий файл (або просто вище компонента):

import { createContext } from 'react';

// Ми створюємо "канал" і можемо дати дефолтне значення 'light'
export const ThemeContext = createContext('light');

Крок 2: Налаштування Провайдера

Огорнемо наш додаток, щоб "роздати Wi-Fi".

import { useState } from 'react';
import { ThemeContext } from './ThemeContext'; // Імпортуємо наш контекст

function App() {
  const [theme, setTheme] = useState('light');

  return (
    // value — це те, що ми транслюємо. Зараз це просто рядок 'light' або 'dark'
    <ThemeContext.Provider value={theme}>
      <div className="app-container">
        <Header />
        <MainContent />
      </div>
      {/* Кнопка для тесту перемикання */}
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Змінити тему
      </button>
    </ThemeContext.Provider>
  );
}

Зауважте: Header та MainContent не отримують жодних пропсів про тему!

Крок 3: Споживання даних (Десь глибоко в MainContent)

Уявіть, що всередині MainContent є картка з текстом. Як їй дізнатися, яку тему вибрано?

Запитання до вас: Що станеться, якщо ми спробуємо використати useContext за межами <ThemeContext.Provider>? (Відповідь: Ми отримаємо дефолтне значення, яке передали в createContext('light'), або undefined, якщо нічого не передали).

Ось код споживача:

import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function TextCard() {
  // МАГІЯ ТУТ 👇
  const theme = useContext(ThemeContext);

  const style = {
    backgroundColor: theme === 'light' ? '#fff' : '#333',
    color: theme === 'light' ? '#000' : '#fff',
    padding: '20px',
    margin: '20px'
  };

  return (
    <div style={style}>
      <h2>Я знаю, яка зараз тема!</h2>
      <p>Поточна тема: {theme}</p>
    </div>
  );
}

Чому це круто? TextCard може бути вкладеним на 50 рівнів глибини. Йому байдуже. Як тільки в App зміниться state theme, TextCard миттєво перемалюється з новим кольором.


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

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

Завдання 1: "Hello Context" 1. Створіть контекст UserContext. 2. У App створіть змінну const user = "David Malan". 3. Огорніть все в UserContext.Provider і передайте туди ім'я. 4. Створіть компонент WelcomeMessage, який виводить <h1>Привіт, {ім'я}!</h1>, використовуючи useContext.

Завдання 2: "Динаміка" 1. Змініть user в App на стан (useState), щоб ім'я можна було міняти. 2. Додайте <input>, який змінює ім'я користувача в реальному часі. 3. Переконайтеся, що WelcomeMessage оновлюється автоматично при введенні тексту.

Завдання 3: "Повний контроль" (Рівень PRO) Часто ми хочемо не тільки читати дані, а й змінювати їх з глибини. * Передайте в value Провайдера об'єкт: {{ user, setUser }}. * Створіть компонент UserProfile десь глибоко, який має кнопку "Вийти" (змінює user на null або "Гість"). * Використайте деструктуризацію: const { setUser } = useContext(UserContext).

Завдання 4: Міні-кейс "Мовний перемикач" Створіть LanguageContext. У додатку має бути дві кнопки зверху: 🇺🇦 та 🇬🇧. Створіть компонент із текстом, який змінюється залежно від обраної мови ("Привіт" / "Hello").

Завдання 5: "А що, якщо..." Що буде, якщо вкласти один Провайдер в інший? Наприклад, ThemeContext всередині UserContext? Спробуйте це реалізувати. Чи поламається додаток? (Спойлер: Ні, це стандартна практика!).


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

Ви тепер знаєте, як користуватися молотком. Але не все навколо — цвяхи.

Типові помилки новачків:

  1. Пхати все в Context.

    • Помилка: Ви зберігаєте кожне натискання клавіші або стан відкриття кожного меню в глобальному контексті.
    • Наслідок: Коли змінюється контекст, перемальовуються ВСІ компоненти, які його використовують. Це вбиває продуктивність.
    • Як треба: Локальний стан (useState) — для локальних речей. Глобальний (Context) — для речей, які справді потрібні багатьом (тема, юзер, мова, кошик покупок).
  2. Забувати про дефолтне значення.

    • Якщо ви спробуєте отрендерити компонент без Провайдера (наприклад, у тестах), ваш код впаде з помилкою, якщо ви не обробили undefined.

Порада від сеньйора:

Досвідчені розробники часто створюють кастомні хуки для контексту, щоб не імпортувати useContext і сам Context щоразу.

Замість:

const theme = useContext(ThemeContext);

Вони пишуть у файлі контексту:

export const useTheme = () => useContext(ThemeContext);

І в компонентах просто:

const theme = useTheme();

Це чистіше, безпечніше і виглядає професійно.


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? 1. Зрозуміли, що передавати пропси через 10 рук — це погана ідея. 2. Навчилися створювати "телепорт" для даних за допомогою createContext. 3. Налаштували "радіостанцію" через Provider. 4. Налаштували "приймачі" через useContext.

Тепер ви вмієте: ✅ Уникати Prop Drilling. ✅ Створювати глобальні налаштування для всього додатку. ✅ Робити код чистішим і більш модульним.

Що далі? Context API чудовий для простих речей, які рідко змінюються (тема, юзер). Але що, якщо у вас складний інтернет-магазин з купою динамічних даних, кешуванням і асинхронністю? На наступному уроці ми глянемо в бік Redux Toolkit або Zustand — важкої артилерії для керування станом.

А поки що — практикуйтесь! Це був CS50... тобто, урок про Context API. 😉