JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Сравнение операторов RxJS с методами массивов/React useEf...

Сравнение операторов RxJS с методами массивов/React useEffect

Модуль 4: Node.js, Next.js и Angular
17 уровень , 8 лекция
Открыта

1. Введение

Если вы уже программировали на JavaScript и работали с массивами, то наверняка знаете такие методы, как map, filter, reduce. Они позволяют элегантно и коротко трансформировать и фильтровать данные. RxJS — это не просто "ещё одна библиотека", а целый реактивный подход, где данные текут в виде потоков (Observable), а методы работы с ними очень напоминают работу с массивами.

С другой стороны, если вы знакомы с React, то знаете, что для работы с побочными эффектами и асинхронными запросами часто используется хук useEffect. В Angular мы чаще всего используем RxJS и его операторы для тех же целей.

Сегодня мы разберём, как эти подходы связаны, в чём их различия и почему RxJS — это не просто "map для массивов на стероидах".

Методы массивов: map, filter, reduce — быстрый обзор

Давайте вспомним, как работают знакомые методы массивов:

const numbers = [1, 2, 3, 4, 5];

// map — трансформация каждого элемента
const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8, 10]

// filter — фильтрация по условию
const even = numbers.filter(x => x % 2 === 0); // [2, 4]

// reduce — свёртка к одному значению
const sum = numbers.reduce((acc, x) => acc + x, 0); // 15

Все эти методы работают с уже существующим массивом — то есть с фиксированным набором данных, который не меняется во времени.

2. RxJS: map, filter и другие операторы

RxJS работает с потоками данных, которые могут приходить асинхронно и потенциально бесконечно (например, события пользователя, ответы сервера, таймеры). Но операторы RxJS (map, filter, reduce и многие другие) по синтаксису и логике очень похожи на методы массивов.

Пример: map и filter в RxJS

import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';

of(1, 2, 3, 4, 5)
  .pipe(
    map(x => x * 2),       // удваиваем каждый элемент
    filter(x => x > 5)     // фильтруем > 5
  )
  .subscribe(result => console.log(result)); // 6, 8, 10

В чём разница?

  • Методы массивов работают сразу со всеми элементами — массив уже есть.
  • Операторы RxJS работают с каждым элементом потока по мере его поступления (асинхронно, лениво).

Таблица соответствия: методы массива vs операторы RxJS

Операция Array method RxJS оператор Пример RxJS
Трансформация
map
map
obs$.pipe(map(x => x * 2))
Фильтрация
filter
filter
obs$.pipe(filter(x => x > 0))
Свёртка
reduce
reduce
,
scan
obs$.pipe(reduce(...))
Поиск
find
find
obs$.pipe(find(x => x === 42))
Проверка
some
some
obs$.pipe(some(x => x > 10))
Все элементы
every
every
obs$.pipe(every(x => x !== null))
Перебор
forEach
tap
obs$.pipe(tap(x => ...))

Важно: В RxJS большинство операторов работают с каждым элементом потока отдельно, а не с "всеми сразу". Некоторые (например, reduce) ждут завершения потока.

3. Примеры: как привычные задачи решаются в RxJS

Пример 1: Фильтрация и трансформация массива

На массивах:

const users = [
  { name: 'Alice', age: 22 },
  { name: 'Bob', age: 17 },
  { name: 'Eve', age: 34 }
];

const adults = users
  .filter(u => u.age >= 18)
  .map(u => u.name);

console.log(adults); // ['Alice', 'Eve']

В RxJS:

import { from } from 'rxjs';
import { filter, map } from 'rxjs/operators';

from(users)
  .pipe(
    filter(u => u.age >= 18),
    map(u => u.name)
  )
  .subscribe(name => console.log(name)); // 'Alice', 'Eve'

Пример 2: Асинхронные данные (например, HTTP-запрос)

В Angular с RxJS:

this.http.get<User[]>('/api/users')
  .pipe(
    map(users => users.filter(u => u.active)),
    map(users => users.map(u => u.name))
  )
  .subscribe(names => {
    this.activeUserNames = names;
  });

В React (useEffect + методы массива):

useEffect(() => {
  fetch('/api/users')
    .then(res => res.json())
    .then(users => {
      const activeUserNames = users
        .filter(u => u.active)
        .map(u => u.name);
      setActiveUserNames(activeUserNames);
    });
}, []);

4. useEffect в React vs RxJS-подход в Angular

Как работает useEffect?

В React хук useEffect используется для побочных эффектов — например, загрузки данных, подписки на события, очистки ресурсов.

useEffect(() => {
  // побочный эффект — например, запрос данных
  fetch('/api/data')
    .then(res => res.json())
    .then(setData);

  // опционально: функция очистки
  return () => {
    // cleanup
  };
}, []);
  • useEffect вызывается после рендера компонента.
  • Можно подписаться на события, загрузить данные, подписаться на WebSocket и т.д.
  • Для автоматической отписки возвращается функция очистки.

В чём разница с RxJS?

  • В Angular мы обычно не пишем "эффекты" в компоненте напрямую, а работаем с Observables, которые автоматически управляют подписками (особенно с async pipe).
  • RxJS позволяет строить сложные цепочки обработки данных (например, debounce, switchMap, retry и т.д.) прямо в потоке.
  • В React, чтобы реализовать сложные цепочки, приходится писать много вложенных then/catch или использовать сторонние библиотеки.

Сравнение на примере:

React:

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(setData);

  return () => controller.abort();
}, []);

Angular:

data$ = this.http.get('/api/data').pipe(
  catchError(err => of([]))
);

// В шаблоне:
// <div *ngFor="let item of data$ | async">{{ item }}</div>
  • В Angular с async pipe подписка и отписка происходят автоматически.
  • RxJS позволяет легко комбинировать потоки, отменять старые запросы (switchMap), дебаунсить ввод пользователя и т.д.

5. Полезные нюансы

Типичные задачи: RxJS или useEffect?

Задача RxJS (Angular) useEffect (React)
Подписка на поток данных Observable + async pipe useEffect + setState
Асинхронный запрос с отменой старого switchMap, takeUntil, debounce useEffect + AbortController
Трансформация и фильтрация данных map, filter, scan, reduce методы массива в then()
Множественные источники данных combineLatest, merge, zip useEffect + Promise.all
Очистка ресурсов auto (async pipe) или unsubscribe return cleanup в useEffect

Аналогии и отличия: когда что использовать?

  • Методы массива — для работы с уже существующими, фиксированными данными.
  • RxJS-операторы — для работы с потоками данных, которые могут быть асинхронными, потенциально бесконечными, приходить частями (например, события пользователя, данные с сервера, WebSocket).
  • useEffect — для обработки побочных эффектов в React-компонентах (загрузка данных, подписки, таймеры).

RxJS = суперзаряженный "массив", элементы которого могут прилетать в любой момент, а вы можете трансформировать, фильтровать, комбинировать эти элементы на лету.

6. Типичные ошибки при сравнении RxJS, методов массива и useEffect

Ошибка №1: Ожидание, что RxJS-операторы работают как методы массива
RxJS-операторы работают асинхронно и лениво — данные "текут", а не "лежат". Если вы ждёте, что результат сразу будет массивом — вы удивитесь, что ничего не происходит, пока не подпишетесь.

Ошибка №2: Попытка использовать методы массива на Observable
Методы массива (map, filter) нельзя вызывать напрямую на Observable — только внутри .pipe(). Например, obs$.map() не работает.

Ошибка №3: Забыли отписаться от Observable
В Angular это решает async pipe, но если подписываетесь вручную — не забудьте про unsubscribe(). В React с useEffect — не забывайте про функцию очистки.

Ошибка №4: Смешивание синхронных и асинхронных подходов
Пытаться использовать RxJS-операторы для массивов или наоборот — методы массива для потоков — приведёт к ошибкам типов и недопониманию.

Ошибка №5: Ожидание, что useEffect автоматически отменяет асинхронные операции
В React вы сами должны управлять отменой (например, через AbortController), иначе получите "утечки" или гонки данных.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ