Ось готовий урок, створений за твоїм майстер-промптом.
🏛 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:
createContext()- Це створення самої "мережі". Ми кажемо React: "Гей, ми хочемо мати канал для передачі теми (світла/темна)".
Provider(Провайдер/Постачальник)- Це компонент-обгортка (наш Роутер). Він каже: "Все, що знаходиться всередині мене, може отримати доступ до цих даних".
- Головне правило: Провайдер має бути вище у дереві компонентів, ніж ті, хто хоче читати дані.
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. 💡 Мислення як у розробника
Ви тепер знаєте, як користуватися молотком. Але не все навколо — цвяхи.
Типові помилки новачків:
-
Пхати все в Context.
- Помилка: Ви зберігаєте кожне натискання клавіші або стан відкриття кожного меню в глобальному контексті.
- Наслідок: Коли змінюється контекст, перемальовуються ВСІ компоненти, які його використовують. Це вбиває продуктивність.
- Як треба: Локальний стан (
useState) — для локальних речей. Глобальний (Context) — для речей, які справді потрібні багатьом (тема, юзер, мова, кошик покупок).
-
Забувати про дефолтне значення.
- Якщо ви спробуєте отрендерити компонент без Провайдера (наприклад, у тестах), ваш код впаде з помилкою, якщо ви не обробили
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. 😉