Модуль 14

Підйом стану (lifting state up)

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


🏛 CS50: Основи архітектури застосунків

Тема: Підйом стану (Lifting State Up)

Всім привіт! Радий вас бачити. 👋

Сьогодні ми поговоримо про одну з найважливіших концепцій у розробці компонентних інтерфейсів (особливо в React, Vue або Svelte). Це той момент, коли ви переходите від написання просто "коду, що працює" до "архітектури, яка масштабується".

Тема звучить трохи пафосно — "Підйом стану" (Lifting State Up). Але насправді це просто красива назва для здорового глузду.


1. 🔥 Вступ: Проблема «глухих телефонів»

Уявіть ситуацію. Ви пишете застосунок для бронювання квитків. У вас є два компоненти на екрані: 1. Лічильник квитків (де ви натискаєте "+1"). 2. Загальна ціна (яка має показувати: "До сплати: 100 грн").

Ви натискаєте кнопку "+" у першому компоненті. Чи знає про це другий компонент (Ціна)? Ні. Вони як сусіди в багатоквартирному будинку, які не мають телефонів один одного. Вони живуть поруч, але ізольовано.

🤔 Питання до вас: Як змусити "Ціну" дізнатися, що "Лічильник" змінився?

Можна спробувати з'єднати їх дротами напряму? В спагеті-коді — так. Але в сучасній розробці компоненти-сусіди (sibling components) не спілкуються напряму. Це табу.

Чому? Тому що якщо кожен компонент почне смикати сусіда, ми отримаємо заплутану павутину. Якщо один зламається — впаде все.

Аналогія: Уявіть двох дітей у кімнаті, які хочуть домовитися, яку піцу замовити. Вони не можуть дзвонити в піцерію самі. Вони кричать: "Мам, я хочу пепероні!". Мама (батьківський компонент) чує це, приймає рішення і каже всім: "Окей, ми замовляємо пепероні".

Це і є суть Підйому стану: коли дітям потрібна спільна інформація, ми забираємо її у них і віддаємо батькам.


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

Давайте розберемо це без складних термінів.

У компонентному підході потік даних зазвичай односпрямований (one-way data flow). Дані течуть зверху вниз, як водоспад 🌊.

🔑 Ключові поняття:

  1. State (Стан): Це "пам'ять" компонента. Наприклад, число на лічильнику або текст в інпуті.
  2. Props (Пропси/Властивості): Це дані, які батько передає дитині. Дитина не може їх змінити, вона їх тільки "читає".
  3. Lifting Up (Підйом): Це процес переміщення State з дочірнього компонента вгору до найближчого спільного предка (Parent).

Як це працює логічно:

  1. Ми знаходимо спільного предка для двох компонентів.
  2. Оголошуємо дані (State) саме в цьому Предку.
  3. Предок передає ці дані вниз обом дітям через Props.
  4. А щоб діти могли змінювати дані? Предок передає їм ще й функцію (callback). Дитина викликає функцію → Предок змінює State → Нові дані течуть вниз до всіх дітей.

❗️ Запам’ятайте: Єдине джерело істини (Single Source of Truth). Дані про кількість квитків не повинні жити і в лічильнику, і в ціні. Вони живуть в одному місці (зверху), а всі інші просто отримують копію для відображення.


3. 🧪 Приклади (Код говоритиме за себе)

Давайте подивимось на прикладі React (але логіка однакова всюди).

Приклад 1: Проблема (Ізоляція)

У нас є два інпути. Ми хочемо, щоб вони були синхронізовані (дзеркальні). Пишеш в одному — з'являється в іншому.

// ❌ НЕПРАВИЛЬНО: Кожен сам за себе
function InputA() {
  const [text, setText] = useState(""); // Свій власний стан
  return <input value={text} onChange={e => setText(e.target.value)} />;
}

function InputB() {
  const [text, setText] = useState(""); // Свій власний стан, ніяк не пов'язаний з A
  return <input value={text} onChange={e => setText(e.target.value)} />;
}

function App() {
  return (
    <div>
      <InputA />
      <InputB />
    </div>
  );
}

Питання: Що станеться, якщо я напишу "Привіт" у InputA? Відповідь: InputB залишиться пустим. Вони не знають про існування один одного.


Приклад 2: Рішення (Підйом стану)

Давайте "піднімемо" стан text у компонент App.

// ✅ ПРАВИЛЬНО: Батько керує всім

// 1. Дочірній компонент став "дурним" (stateless).
// Він просто показує те, що дали (value), і скаржиться батькові при змінах (onChange).
function MirrorInput({ value, onInputChange }) {
  return (
    <input
      value={value}
      onChange={(e) => onInputChange(e.target.value)}
      style={{ border: "2px solid #61dafb", margin: "10px" }}
    />
  );
}

// 2. Батько (App) тримає стан
function App() {
  const [sharedText, setSharedText] = useState(""); // 🔥 Стан тепер тут!

  return (
    <div style={{ padding: "20px" }}>
      <h3>Дзеркальні інпути</h3>

      {/* Ми передаємо стан вниз і функцію для його зміни */}
      <MirrorInput value={sharedText} onInputChange={setSharedText} />
      <MirrorInput value={sharedText} onInputChange={setSharedText} />

      <p>Спільний текст: {sharedText}</p>
    </div>
  );
}

Чому це працює? Коли ви друкуєте в першому інпуті, він не змінює себе сам. Він викликає setSharedText. React оновлює стан в App. App перерисовується і посилає новий текст в ОБИДВА інпути одночасно.


Приклад 3: Реальна задача (Конвертер валют)

Трохи ускладнимо. Один вводить гривні, інший показує долари.

function CurrencyConverter() {
  const [amount, setAmount] = useState(0); // Стан у батька (в гривнях)

  const handleUAHChange = (val) => setAmount(val);
  const handleUSDChange = (val) => setAmount(val * 40); // Спрощено: 1$ = 40₴

  return (
    <div>
      <label>Гривні: </label>
      {/* Цей просто показує суму */}
      <input 
        type="number" 
        value={amount} 
        onChange={(e) => handleUAHChange(e.target.value)} 
      />

      <br />

      <label>Долари: </label>
      {/* Цей отримує суму, ділить на 40 і показує */}
      <input 
        type="number" 
        value={amount / 40} 
        onChange={(e) => handleUSDChange(e.target.value)} 
      />
    </div>
  );
}

Бачите? Стан один (amount у гривнях), але відображення різне.


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

Час кодити! Не просто читайте, відкрийте редактор (CodePen/CodeSandbox).

Завдання 1: Повторення Створіть два компоненти: <Button /> і <Display />. * <Display /> показує число. * <Button /> збільшує це число. * Стан має бути у батьківського компонента.

Завдання 2: "Акордеон" (Класика) У вас є 3 компоненти <Panel title="Тема 1">Текст...</Panel>. * Зробіть так, щоб одночасно могла бути відкрита лише одна панель. * Якщо я відкриваю другу, перша повинна закритися автоматично. * Підказка: Батько повинен зберігати ID активної панелі (activeId).

Завдання 3: Фільтр списку Створіть масив продуктів: ['Яблуко', 'Банан', 'Апельсин', 'Груша']. * Компонент SearchBar (поле пошуку). * Компонент ProductList (список ul > li). * Зв'яжіть їх так, щоб введення тексту у SearchBar фільтрувало ProductList. Де повинен жити текст пошуку?

Завдання 4: Міні-кейс Уявіть кошик інтернет-магазину. * Є список товарів (у кожного своя кнопка "Видалити"). * Є "Хедер", де пишеться "Кількість товарів: N". * Реалізуйте видалення товару так, щоб цифра в Хедері оновлювалася.


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

Як зрозуміти, коли треба піднімати стан?

Початківці часто роблять помилку: вони дублюють стан.

"Я створю стейт count у кнопці, а потім ще один стейт count у дисплеї і буду намагатися їх синхронізувати." ⛔️ Стоп. Це шлях до пекла багів.

Як думає досвідчений розробник (Senior Mindset):

  1. Він дивиться на макет і питає: "Які компоненти залежать від цих даних?"
  2. Якщо компонент тільки один — стан живе в ньому.
  3. Якщо компонентів два і більше (і вони не батько-дитина, а сусіди) — він шукає їхнього найближчого спільного родича.
  4. Він піднімає стан туди.
  5. Він "опускає" дані вниз через пропси.

Порада: Краще підняти стан занадто високо, ніж дублювати його. "Дурні" компоненти (які тільки отримують пропси) легше тестувати і підтримувати.


6. 🧩 Підсумок

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

  1. Сусідні компоненти не спілкуються напряму.
  2. Щоб поділитися даними, ми піднімаємо стан до спільного батька.
  3. Батько стає "Єдиним джерелом істини".
  4. Дані течуть вниз (Props), події — вгору (Callbacks).

Що ви тепер вмієте: Ви можете створювати інтерактивні інтерфейси, де зміна в одному кутку екрану миттєво і коректно оновлює інший куток. Без магії, суто на логіці.

🚀 Тизер наступного уроку: Але стривайте... Що, якщо цей "спільний предок" знаходиться на 10 рівнів вище? Невже нам доведеться передавати пропси через 10 проміжних компонентів, як естафетну паличку? Це називається Prop Drilling, і це жахливо. На наступному уроці ми дізнаємось, як телепортувати дані крізь компоненти за допомогою Context API.

Це було CS50 (в душі). До зустрічі! 💻