Ось готовий урок, створений за твоїм майстер-промптом.
🏛 CS50: Основи архітектури застосунків
Тема: Підйом стану (Lifting State Up)
Всім привіт! Радий вас бачити. 👋
Сьогодні ми поговоримо про одну з найважливіших концепцій у розробці компонентних інтерфейсів (особливо в React, Vue або Svelte). Це той момент, коли ви переходите від написання просто "коду, що працює" до "архітектури, яка масштабується".
Тема звучить трохи пафосно — "Підйом стану" (Lifting State Up). Але насправді це просто красива назва для здорового глузду.
1. 🔥 Вступ: Проблема «глухих телефонів»
Уявіть ситуацію. Ви пишете застосунок для бронювання квитків. У вас є два компоненти на екрані: 1. Лічильник квитків (де ви натискаєте "+1"). 2. Загальна ціна (яка має показувати: "До сплати: 100 грн").
Ви натискаєте кнопку "+" у першому компоненті. Чи знає про це другий компонент (Ціна)? Ні. Вони як сусіди в багатоквартирному будинку, які не мають телефонів один одного. Вони живуть поруч, але ізольовано.
🤔 Питання до вас: Як змусити "Ціну" дізнатися, що "Лічильник" змінився?
Можна спробувати з'єднати їх дротами напряму? В спагеті-коді — так. Але в сучасній розробці компоненти-сусіди (sibling components) не спілкуються напряму. Це табу.
Чому? Тому що якщо кожен компонент почне смикати сусіда, ми отримаємо заплутану павутину. Якщо один зламається — впаде все.
Аналогія: Уявіть двох дітей у кімнаті, які хочуть домовитися, яку піцу замовити. Вони не можуть дзвонити в піцерію самі. Вони кричать: "Мам, я хочу пепероні!". Мама (батьківський компонент) чує це, приймає рішення і каже всім: "Окей, ми замовляємо пепероні".
Це і є суть Підйому стану: коли дітям потрібна спільна інформація, ми забираємо її у них і віддаємо батькам.
2. 🧠 Теоретична база (Що там «під капотом»?)
Давайте розберемо це без складних термінів.
У компонентному підході потік даних зазвичай односпрямований (one-way data flow). Дані течуть зверху вниз, як водоспад 🌊.
🔑 Ключові поняття:
- State (Стан): Це "пам'ять" компонента. Наприклад, число на лічильнику або текст в інпуті.
- Props (Пропси/Властивості): Це дані, які батько передає дитині. Дитина не може їх змінити, вона їх тільки "читає".
- Lifting Up (Підйом): Це процес переміщення
Stateз дочірнього компонента вгору до найближчого спільного предка (Parent).
Як це працює логічно:
- Ми знаходимо спільного предка для двох компонентів.
- Оголошуємо дані (State) саме в цьому Предку.
- Предок передає ці дані вниз обом дітям через
Props. - А щоб діти могли змінювати дані? Предок передає їм ще й функцію (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):
- Він дивиться на макет і питає: "Які компоненти залежать від цих даних?"
- Якщо компонент тільки один — стан живе в ньому.
- Якщо компонентів два і більше (і вони не батько-дитина, а сусіди) — він шукає їхнього найближчого спільного родича.
- Він піднімає стан туди.
- Він "опускає" дані вниз через пропси.
Порада: Краще підняти стан занадто високо, ніж дублювати його. "Дурні" компоненти (які тільки отримують пропси) легше тестувати і підтримувати.
6. 🧩 Підсумок
Отже, що ми сьогодні зрозуміли?
- Сусідні компоненти не спілкуються напряму.
- Щоб поділитися даними, ми піднімаємо стан до спільного батька.
- Батько стає "Єдиним джерелом істини".
- Дані течуть вниз (Props), події — вгору (Callbacks).
Що ви тепер вмієте: Ви можете створювати інтерактивні інтерфейси, де зміна в одному кутку екрану миттєво і коректно оновлює інший куток. Без магії, суто на логіці.
🚀 Тизер наступного уроку: Але стривайте... Що, якщо цей "спільний предок" знаходиться на 10 рівнів вище? Невже нам доведеться передавати пропси через 10 проміжних компонентів, як естафетну паличку? Це називається Prop Drilling, і це жахливо. На наступному уроці ми дізнаємось, як телепортувати дані крізь компоненти за допомогою Context API.
Це було CS50 (в душі). До зустрічі! 💻