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 |
|---|---|---|---|
| Трансформация | |
|
|
| Фильтрация | |
|
|
| Свёртка | |
, |
|
| Поиск | |
|
|
| Проверка | |
|
|
| Все элементы | |
|
|
| Перебор | |
|
|
Важно: В 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), иначе получите "утечки" или гонки данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ