Ось готовий урок, створений спеціально для тебе у стилі CS50. Приготуйся, сьогодні ми розберемося з тим, що змушує наші додатки "жити".
🎓 Урок: State Management (Управління станом)
Привіт, світе! Це CS50 (умовно 😉), і сьогодні ми говоримо про тему, яка перетворює набір статичних картинок на справжній інтерактивний додаток. Ми говоримо про State Management.
1. 🔥 Вступ: Чому все ламається?
Уявіть собі звичайну шкільну дошку. На ній написано: "Черговий сьогодні — Андрій". Увесь клас дивиться на дошку і знає: Андрій черговий.
А тепер уявіть, що у кожного учня є свій власний зошит, де він записав ім’я чергового. Вчитель каже: "Зміна планів! Чергова — Марія!". Вчитель витирає напис на дошці і пише нове ім'я.
Але що відбувається в зошитах? Там досі записаний "Андрій". У нас виникла розсинхронізація. Один учень думає одне, інший — інше, а реальність — третя.
У програмуванні це класична проблема:
Коли ви натискаєте "Лайк" під постом, сердечко має стати червоним, лічильник має збільшитися на +1, а в вашому профілі цей пост має з'явитися в "Улюблених".
❓ Питання до вас: Як зробити так, щоб ці три різні частини екрана дізналися про одну й ту ж саму подію одночасно і не брехали користувачу?
Ось тут на сцену виходить State Management. Без нього ваш додаток — це просто набір глухих кімнат, які не знають, що відбувається у сусідів.
2. 🧠 Теоретична база: Що "під капотом"?
Давайте домовимося про терміни.
Що таке State (Стан)?
State — це "знімок правди" про ваш додаток у конкретний момент часу. Це дані, які можуть змінюватися: * Чи відкрите меню? (true/false) * Текст у полі вводу. * Список товарів у кошику. * Завантажились дані чи ще крутиться спінер?
Головне правило: Single Source of Truth
Уявіть, що State — це вода 💧. У хорошій архітектурі вода тече зверху вниз (від батьківських компонентів до дочірніх).
Є три рівні, які треба розрізняти (запам’ятайте це!):
- Local State (Локальний стан): Потрібен тільки тут і зараз. Приклад: чи відкритий цей конкретний випадаючий список. Нікому іншому в додатку це знати не треба.
- Global State (Глобальний стан): Потрібен багатьом частинам додатку. Приклад: хто зараз залогінений (User Profile) або що лежить у кошику.
- Server State (Серверний стан): Це дані, які ми взяли з бази даних. Приклад: список твітів. Насправді це не ваш стан, це просто кеш того, що лежить на сервері.
Як це працює?
Коли State змінюється, інтерфейс (UI) повинен перемалюватися (Re-render). Формула проста: $$ UI = f(State) $$ Ваш екран — це просто функція від даних. Змінили дані -> змінилася картинка.
3. 🧪 Приклади: Від простого до болючого
Давайте подивимось на код (псевдокод, схожий на React/JS, бо це стандарт індустрії).
Рівень 1: Локальний стан (Все добре)
У нас є кнопка-лічильник.
function Counter() {
// Змінна "count" починається з 0
let [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Натиснуто разів: {count}
</button>
);
}
Тут все просто. Стан живе всередині кнопки. Ніхто зовні його не чіпає.
Рівень 2: Проблема "Prop Drilling" (Біль)
Уявіть, що у нас є сторінка магазину. Структура така:
App -> Header -> UserMenu -> Avatar.
Ми хочемо показати аватарку користувача. Дані про користувача ми отримуємо в самому верху, в App.
❓ Питання: Як передати дані про аватарку з App аж в Avatar?
Очікування новачка: Аватарка сама якось дізнається. Реальність: Нам треба передавати дані як естафетну паличку через кожен компонент.
// App (має user)
// ⬇️ передає в Header
// Header (йому user не треба, але він мусить передати далі)
// ⬇️ передає в UserMenu
// UserMenu (теж просто кур'єр)
// ⬇️ передає в Avatar
// Avatar (нарешті показує картинку!)
Це називається Prop Drilling. Це жахливо, бо якщо ми захочемо перемістити аватарку, нам доведеться переписувати 4 файли.
Рівень 3: Глобальний Store (Рішення)
Ми створюємо "Склад" (Store) десь збоку від нашого дерева компонентів. Будь-який компонент може підключитися до Складу напряму.
Аналогія: замість того, щоб передавати відро води по ланцюжку пожежників, ми просто провели водопровід (трубу) прямо до потрібного місця.
// 1. Створюємо контекст (магазин)
const UserContext = createStore();
// 2. Десь глибоко внизу
function Avatar() {
// "Телепортуємо" дані прямо сюди
const user = useContext(UserContext);
return <img src={user.photo} />;
}
Тепер Header і UserMenu навіть не знають, що крізь них пролітають якісь дані. Чистота і порядок!
4. 🛠 Практична частина
Час закачати рукави. Виконайте ці завдання (подумки або у редакторі коду):
- 🔹 Повтори: Напиши (або намалюй схему) компонента "Світлофор". У нього має бути стан:
red,yellowабоgreen. Кнопка "Next" має перемикати їх по колу. - 🔹 Зміни умови: Додай до світлофора ще один стан:
broken(миготить жовтим). Як ти будеш переходити в цей стан? (Окрема кнопка?) - 🔹 Виправ помилку:
- Код:
state.count = 5;(пряме присвоєння). - Чому це погано? (Підказка: чи дізнається UI про те, що треба оновитися?)
- Як виправити?
- Код:
- 🔹 Реальна задача: У тебе є список товарів (
ProductList) і кошик у шапці сайту (CartIcon). Коли ти тиснеш "Купити" внизу сторінки, цифра вгорі має змінитися.- Питання: Де має жити стан "список куплених товарів"? У
ProductList? УCartIcon? Чи десь вище над ними обома?
- Питання: Де має жити стан "список куплених товарів"? У
- 🔹 Міні-кейс: Спроєктуй (на папері) стан для форми реєстрації з 3 кроків (Wizard).
- Крок 1: Ім'я/Email.
- Крок 2: Адреса.
- Крок 3: Оплата.
- Челлендж: Якщо користувач повертається з кроку 2 на крок 1, дані не мають зникнути.
- 🔹 "А що, якщо...": Користувач відкрив твій сайт у двох вкладках браузера. В одній він змінив тему на "Темну". Що має статися в іншій вкладці? Як би ти це синхронізував?
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в управлінні станом?
❌ Помилка новачка: Запихати ВСЕ в глобальний стейт (Redux/Context). "Чи відкрито це маленьке меню?" — в глобальний стейт! "Текст, який я зараз друкую в інпуті" — в глобальний стейт! Це перетворює додаток на смітник. Будь-яка зміна призводить до перемальовки всього додатку.
✅ Думка профі: Colocation (Спільне розташування). Тримай стан якомога ближче до місця, де він використовується. Якщо дані потрібні тільки цьому компоненту — залиш їх тут. Якщо потрібні цьому і сусіду — підніми їх до спільного батька (Lifting State Up). І тільки якщо дані потрібні на різних кінцях галактики — роби їх глобальними.
❌ Помилка новачка: Дублювання стану.
state = {
firstName: "Ivan",
lastName: "Petrenko",
fullName: "Ivan Petrenko" // 🛑 ЗАЙВЕ!
}
✅ Думка профі:
Вираховуй дані на льоту (Derived State).
fullName — це не стан, це результат firstName + lastName. Якщо зміниться ім'я, а ти забудеш оновити fullName, у тебе буде баг. Не зберігай те, що можна обчислити.
6. 🧩 Підсумок
Отже, що ми сьогодні зрозуміли:
- UI без State мертвий. Стан — це серцебиття додатку.
- Single Source of Truth. Не дублюйте дані. Нехай буде одне джерело правди.
- Prop Drilling — це зло, якого можна уникнути за допомогою Context або State Managers.
- Тримай стан близько. Не роби все глобальним.
Тепер ви вмієте: Дивитися на макет сайту і бачити не просто прямокутники, а потоки даних. Ви можете сказати: "Ага, ось тут натиснули, дані полетіли вгору в Store, а потім спустилися вниз у Header".
🔜 Наступного разу: Ми поговоримо про те, що робити, коли State живе не у нас, а на іншому комп'ютері (сервері). Готуйтеся до теми "API та Асинхронність: як не заморозити інтерфейс, поки чекаєш відповідь".
А поки що... це був CS50! 👋