Модуль 26

State Management: підходи та best practices

Ось готовий урок, створений спеціально для тебе у стилі CS50. Приготуйся, сьогодні ми розберемося з тим, що змушує наші додатки "жити".


🎓 Урок: State Management (Управління станом)

Привіт, світе! Це CS50 (умовно 😉), і сьогодні ми говоримо про тему, яка перетворює набір статичних картинок на справжній інтерактивний додаток. Ми говоримо про State Management.


1. 🔥 Вступ: Чому все ламається?

Уявіть собі звичайну шкільну дошку. На ній написано: "Черговий сьогодні — Андрій". Увесь клас дивиться на дошку і знає: Андрій черговий.

А тепер уявіть, що у кожного учня є свій власний зошит, де він записав ім’я чергового. Вчитель каже: "Зміна планів! Чергова — Марія!". Вчитель витирає напис на дошці і пише нове ім'я.

Але що відбувається в зошитах? Там досі записаний "Андрій". У нас виникла розсинхронізація. Один учень думає одне, інший — інше, а реальність — третя.

У програмуванні це класична проблема: Коли ви натискаєте "Лайк" під постом, сердечко має стати червоним, лічильник має збільшитися на +1, а в вашому профілі цей пост має з'явитися в "Улюблених".

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

Ось тут на сцену виходить State Management. Без нього ваш додаток — це просто набір глухих кімнат, які не знають, що відбувається у сусідів.


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

Давайте домовимося про терміни.

Що таке State (Стан)?

State — це "знімок правди" про ваш додаток у конкретний момент часу. Це дані, які можуть змінюватися: * Чи відкрите меню? (true/false) * Текст у полі вводу. * Список товарів у кошику. * Завантажились дані чи ще крутиться спінер?

Головне правило: Single Source of Truth

Уявіть, що State — це вода 💧. У хорошій архітектурі вода тече зверху вниз (від батьківських компонентів до дочірніх).

Є три рівні, які треба розрізняти (запам’ятайте це!):

  1. Local State (Локальний стан): Потрібен тільки тут і зараз. Приклад: чи відкритий цей конкретний випадаючий список. Нікому іншому в додатку це знати не треба.
  2. Global State (Глобальний стан): Потрібен багатьом частинам додатку. Приклад: хто зараз залогінений (User Profile) або що лежить у кошику.
  3. 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. 🛠 Практична частина

Час закачати рукави. Виконайте ці завдання (подумки або у редакторі коду):

  1. 🔹 Повтори: Напиши (або намалюй схему) компонента "Світлофор". У нього має бути стан: red, yellow або green. Кнопка "Next" має перемикати їх по колу.
  2. 🔹 Зміни умови: Додай до світлофора ще один стан: broken (миготить жовтим). Як ти будеш переходити в цей стан? (Окрема кнопка?)
  3. 🔹 Виправ помилку:
    • Код: state.count = 5; (пряме присвоєння).
    • Чому це погано? (Підказка: чи дізнається UI про те, що треба оновитися?)
    • Як виправити?
  4. 🔹 Реальна задача: У тебе є список товарів (ProductList) і кошик у шапці сайту (CartIcon). Коли ти тиснеш "Купити" внизу сторінки, цифра вгорі має змінитися.
    • Питання: Де має жити стан "список куплених товарів"? У ProductList? У CartIcon? Чи десь вище над ними обома?
  5. 🔹 Міні-кейс: Спроєктуй (на папері) стан для форми реєстрації з 3 кроків (Wizard).
    • Крок 1: Ім'я/Email.
    • Крок 2: Адреса.
    • Крок 3: Оплата.
    • Челлендж: Якщо користувач повертається з кроку 2 на крок 1, дані не мають зникнути.
  6. 🔹 "А що, якщо...": Користувач відкрив твій сайт у двох вкладках браузера. В одній він змінив тему на "Темну". Що має статися в іншій вкладці? Як би ти це синхронізував?

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

Як відрізнити новачка від профі в управлінні станом?

❌ Помилка новачка: Запихати ВСЕ в глобальний стейт (Redux/Context). "Чи відкрито це маленьке меню?" — в глобальний стейт! "Текст, який я зараз друкую в інпуті" — в глобальний стейт! Це перетворює додаток на смітник. Будь-яка зміна призводить до перемальовки всього додатку.

✅ Думка профі: Colocation (Спільне розташування). Тримай стан якомога ближче до місця, де він використовується. Якщо дані потрібні тільки цьому компоненту — залиш їх тут. Якщо потрібні цьому і сусіду — підніми їх до спільного батька (Lifting State Up). І тільки якщо дані потрібні на різних кінцях галактики — роби їх глобальними.

❌ Помилка новачка: Дублювання стану.

state = {
  firstName: "Ivan",
  lastName: "Petrenko",
  fullName: "Ivan Petrenko" // 🛑 ЗАЙВЕ!
}

✅ Думка профі: Вираховуй дані на льоту (Derived State). fullName — це не стан, це результат firstName + lastName. Якщо зміниться ім'я, а ти забудеш оновити fullName, у тебе буде баг. Не зберігай те, що можна обчислити.


6. 🧩 Підсумок

Отже, що ми сьогодні зрозуміли:

  1. UI без State мертвий. Стан — це серцебиття додатку.
  2. Single Source of Truth. Не дублюйте дані. Нехай буде одне джерело правди.
  3. Prop Drilling — це зло, якого можна уникнути за допомогою Context або State Managers.
  4. Тримай стан близько. Не роби все глобальним.

Тепер ви вмієте: Дивитися на макет сайту і бачити не просто прямокутники, а потоки даних. Ви можете сказати: "Ага, ось тут натиснули, дані полетіли вгору в Store, а потім спустилися вниз у Header".

🔜 Наступного разу: Ми поговоримо про те, що робити, коли State живе не у нас, а на іншому комп'ютері (сервері). Готуйтеся до теми "API та Асинхронність: як не заморозити інтерфейс, поки чекаєш відповідь".

А поки що... це був CS50! 👋