Модуль 20

RxJS оператори: map, filter, switchMap

Ось готовий урок, згенерований за твоїм майстер-промптом. Це класичний 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' }.

Завдання:

  1. 🔹 Розігрів: Використовуй filter, щоб отримати тільки замовлення зі статусом 'active'.
  2. 🔹 Трансформація: Використовуй map, щоб після фільтрації отримати не об'єкт, а лише суму замовлення (amount).
  3. 🔹 Ланцюжок: З'єднай це в один pipe. Яка сума буде на виході, якщо вхідні дані: {amount: 100, status: 'canceled'}, {amount: 200, status: 'active'}?
  4. 🔹 Міні-кейс (Level Up): У вас є потік userId$. При кожному новому ID треба робити запит getUserDetails(id), який повертає Observable. Питання: Який оператор ви використаєте (map чи switchMap) і чому? Що станеться, якщо використати неправильний оператор?
  5. 🔹 А що, якщо... Ви використали switchMap для збереження даних при натисканні кнопки "Save". Користувач клікнув двічі швидко. Що станеться з першим збереженням? Це добре чи погано? (Спойлер: це може бути небезпечно для операцій запису!).

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

Як відрізнити новачка від профі в RxJS?

❌ Новачок: * Пише логіку всередині .subscribe(). * Приклад: subscribe(data => { if (data > 5) { ... } }). Ні! Це треба робити в filter! * Не розуміє, чому дані не приходять (забув зробити .subscribe()). Пам'ятайте: Observable лінивий. Поки ви не "підпишетесь" (не замовите піцу), піцу ніхто не почне готувати.

✅ Досвідчений розробник: * Будує декларативні ланцюжки. Він читає код зверху вниз як історію: "Візьми кліки, почекай, відфільтруй короткі, зроби запит". * Знає, що switchMap — це для читання (GET), а для запису (POST/PUT) краще підійдуть mergeMap або concatMap (про них поговоримо пізніше), щоб не скасувати випадково збереження даних.

Порада: Завжди малюйте діаграми (Marble Diagrams) у голові або на папері. Час тече зліва направо. Події — це кульки.


6. 🧩 Підсумок

Отже, що ми сьогодні поклали у свій інструментарій:

  1. map — змінює дані (як кухар нарізає овочі).
  2. filter — відсіює зайве (як фейс-контроль).
  3. switchMap — перемикається на найновішу задачу, скасовуючи старі (як нетерплячий офіціант).

Тепер ви вмієте створювати розумні пошукові рядки та обробляти дані елегантно, не створюючи пекла з колбеків.

🔍 Тизер наступного уроку: Ви помітили, що ми ніде не обробляли помилки? А що, як сервер впаде під час switchMap? Весь наш потік "помер"? На наступному занятті ми розберемо Error Handling (catchError) і дізнаємось, як зробити наші потоки безсмертними.

А поки що — ось вам код, пограйтеся з ним! Це був CS50. Побачимось! 👋