Ось готовий урок, згенерований за твоїм майстер-промптом. Це класичний CS50-вайб: енергійно, з аналогіями та фокусом на розумінні суті.
🎓 RxJS: Магія операторів map, filter та switchMap
Привіт, друзі! Це CS50 (умовно 😉), і сьогодні ми зануримося у світ реактивного програмування.
1. 🔥 Вступ: Проблема «хаосу подій»
Уявіть, що ви розробляєте пошуковий рядок, як у Google. Користувач починає вводити слово "RxJS".
Він натискає: R... потім x... потім J... потім S.
Якщо ви будете відправляти запит на сервер на кожне натискання клавіші, що станеться? 1. Ви "заспамите" свій сервер чотирма запитами. 2. Відповіді можуть прийти у неправильному порядку (наприклад, відповідь на "RxJ" прийде пізніше, ніж на "RxJS"). 3. У результаті користувач ввів "RxJS", а бачить результати для "RxJ". Це катастрофа для UI.
❓ Питання до вас: Як ви вирішували це раніше? Писали купу setTimeout, перевіряли змінні-прапорці (isLoading), скасовували попередні проміси вручну?
Це і є те, що ми називаємо Imperative Hell (імперативне пекло). Ви кажете програмі як керувати часом і станом.
Але що, якби ми могли дивитися на натискання клавіш як на потік води у трубі? Ми могли б поставити фільтри, крани та перемикачі, щоб на виході отримати лише чисту воду (потрібні дані).
Саме для цього нам потрібен RxJS і його оператори. Без них робота з асинхронними подіями (кліки, таймери, запити) перетворюється на спагеті-код.
2. 🧠 Теоретична база (Просто про складне)
У RxJS все є потоком (Stream). Уявіть конвеєр на заводі. * Observable (Спостережуваний об'єкт): Це сам конвеєр, по якому їдуть деталі (дані). * Observer (Спостерігач/Підписник): Це ви в кінці конвеєра, забираєте готовий продукт. * Operators (Оператори): Це роботи-маніпулятори, які стоять уздовж конвеєра і щось роблять з деталями, поки ті їдуть до вас.
Сьогодні ми розберемо трьох найважливіших роботів.
🔹 1. MAP (Трансформатор)
Як це працює: Бере деталь, змінює її форму, кладе назад на конвеєр.
Аналогія: По конвеєру їде картопля. Оператор map чистить і ріже її. Далі по конвеєру їде вже нарізана картопля.
Суть: Вхід $x \rightarrow$ Вихід $f(x)$.
🔹 2. FILTER (Фейс-контроль)
Як це працює: Дивиться на деталь. Якщо вона підходить під умову — пропускає далі. Якщо ні — викидає її в смітник. До вас вона не дійде.
Аналогія: Охоронець у нічному клубі. Тобі є 18? Проходь. Немає? До побачення.
Суть: Вхід $x \rightarrow$ Умова true/false $\rightarrow$ Вихід $x$ (або нічого).
🔹 3. SWITCHMAP (Перемикач)
Це найскладніше, тому увага!
Уявіть, що ви офіціант. Клієнт (потік) дає вам замовлення (подія). Ви йдете на кухню (новий потік/запит).
Але раптом клієнт кричить: "Чекай! Я передумав, хочу інше!"
Що робить switchMap? Він скасовує старе замовлення на кухні (навіть якщо кухар вже почав різати цибулю) і починає виконувати нове.
Суть: Коли приходить нове значення, switchMap відписується від попереднього внутрішнього потоку і підписується на новий. Це вбивця "гонки станів" (race conditions).
3. 🧪 Приклади (Код, який говорить)
Давайте подивимось на код. Припустімо, у нас є потік чисел від 1 до 5.
Приклад 1: Просто map
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
// Джерело: 1, 2, 3
const source$ = of(1, 2, 3);
source$.pipe(
map(x => x * 10) // Множимо на 10
).subscribe(val => console.log(val));
❓ Що ви очікуєте побачити в консолі?
(Подумайте секунду...)
Правильно: 10, 20, 30.
Приклад 2: Додаємо filter
import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const source$ = of(1, 2, 3, 4, 5);
source$.pipe(
filter(x => x % 2 === 0), // Пропускаємо тільки парні
map(x => x * 10) // Множимо їх
).subscribe(val => console.log(val));
Чому такий порядок? Спочатку відсіяли (2, 4), потім помножили. Результат: 20, 40. Якби ми поміняли місцями, ми б множили зайві числа дарма.
Приклад 3: Реальний кейс із switchMap (Пошук)
Уявіть, що keyup$ — це потік натискань клавіш у полі пошуку.
import { fromEvent } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax'; // Імітація запиту на сервер
const input = document.getElementById('search');
const keyup$ = fromEvent(input, 'keyup');
keyup$.pipe(
map((e: any) => e.target.value), // 1. Дістаємо текст з події
switchMap(text => {
// 2. Робимо запит. Якщо прийде нова літера, цей запит скасується!
return ajax.getJSON(`https://api.github.com/users/${text}`);
})
).subscribe(user => console.log(user));
Що тут відбулося?
Ви швидко ввели "dav".
1. "d" -> запускається запит для "d".
2. "da" -> switchMap бачить нове значення! Він обриває запит для "d" і запускає для "da".
3. "dav" -> switchMap обриває запит для "da" і запускає для "dav".
Результат: Сервер не перевантажений, а користувач бачить лише останній актуальний результат. Магія! ✨
4. 🛠 Практична частина
Час розім'яти пальці. У вас є уявний потік orders$ (замовлення в інтернет-магазині), де кожен об'єкт виглядає так: { id: 1, amount: 50, status: 'active' }.
Завдання:
- 🔹 Розігрів: Використовуй
filter, щоб отримати тільки замовлення зі статусом'active'. - 🔹 Трансформація: Використовуй
map, щоб після фільтрації отримати не об'єкт, а лише суму замовлення (amount). - 🔹 Ланцюжок: З'єднай це в один
pipe. Яка сума буде на виході, якщо вхідні дані:{amount: 100, status: 'canceled'},{amount: 200, status: 'active'}? - 🔹 Міні-кейс (Level Up):
У вас є потік
userId$. При кожному новому ID треба робити запитgetUserDetails(id), який повертає Observable. Питання: Який оператор ви використаєте (mapчиswitchMap) і чому? Що станеться, якщо використати неправильний оператор? - 🔹 А що, якщо... Ви використали
switchMapдля збереження даних при натисканні кнопки "Save". Користувач клікнув двічі швидко. Що станеться з першим збереженням? Це добре чи погано? (Спойлер: це може бути небезпечно для операцій запису!).
5. 💡 Мислення як у розробника
Як відрізнити новачка від профі в RxJS?
❌ Новачок:
* Пише логіку всередині .subscribe().
* Приклад: subscribe(data => { if (data > 5) { ... } }). Ні! Це треба робити в filter!
* Не розуміє, чому дані не приходять (забув зробити .subscribe()). Пам'ятайте: Observable лінивий. Поки ви не "підпишетесь" (не замовите піцу), піцу ніхто не почне готувати.
✅ Досвідчений розробник:
* Будує декларативні ланцюжки. Він читає код зверху вниз як історію: "Візьми кліки, почекай, відфільтруй короткі, зроби запит".
* Знає, що switchMap — це для читання (GET), а для запису (POST/PUT) краще підійдуть mergeMap або concatMap (про них поговоримо пізніше), щоб не скасувати випадково збереження даних.
Порада: Завжди малюйте діаграми (Marble Diagrams) у голові або на папері. Час тече зліва направо. Події — це кульки.
6. 🧩 Підсумок
Отже, що ми сьогодні поклали у свій інструментарій:
- map — змінює дані (як кухар нарізає овочі).
- filter — відсіює зайве (як фейс-контроль).
- switchMap — перемикається на найновішу задачу, скасовуючи старі (як нетерплячий офіціант).
Тепер ви вмієте створювати розумні пошукові рядки та обробляти дані елегантно, не створюючи пекла з колбеків.
🔍 Тизер наступного уроку:
Ви помітили, що ми ніде не обробляли помилки? А що, як сервер впаде під час switchMap? Весь наш потік "помер"?
На наступному занятті ми розберемо Error Handling (catchError) і дізнаємось, як зробити наші потоки безсмертними.
А поки що — ось вам код, пограйтеся з ним! Це був CS50. Побачимось! 👋