React Hooks с помощью RxJS и Axios

Reactive Extensions for JavaScript, или RxJS, — это библиотека, которая имеет двойное назначение.
Она создает примитив Observable, который является либо синхронным, либо асинхронным, и включает богатую библиотеку функций, которые можно использовать для создания наблюдаемых, преобразования, фильтрации, объединения и многоадресной передачи наблюдаемых, обеспечивает обработку ошибок и многое другое.

Если это звучит как много — так оно и есть.

Хотя RxJS обычно используется в проектах Angular из-за того, что он является зависимостью от аналогов, его могут упускать из виду инженеры-программисты, создающие приложения с использованием React — или других фронтенд-фреймворков JavaScript, если на то пошло.

Позвольте мне внести ясность — вам не нужно использовать RxJS с React.

Обещания, хук useEffect() и библиотеки, такие как Axios, обеспечивают многое из того, что требуется типичному приложению React для асинхронности и получения данных.
Что RxJS с React действительно дает возможность писать чистые функции для потоков событий, эффективно обрабатывать ошибки в потоке данных и легко получать данные с помощью встроенных API Fetch и WebSocket.

В этой статье я хотел бы поделиться тем, как мы используем RxJS с React в LiveLoveApp для быстрой разработки прототипов и приложений для наших клиентов.

Использование fromFetch()

Одним из преимуществ использования RxJS является предоставляемая функция fromFetch(), которая использует родной Fetch API с отменяемым сигналом AbortController.

Давайте рассмотрим, как вы можете использовать Axios для отмены:

import { get } from "axios";
import { Button } from "@mui/material";
import { useCallback, useEffect, useState } from "react";

export default function App() {
  const [user, setUser] = useState(null);
  const controller = new AbortController();

  useEffect(() => {
    const id = 2;
    get(`https://reqres.in/api/users/${id}`, {
      signal: controller.signal
    }).then((response) => {
      try {
        setUser(response.data.data);
      } catch (e) {
        console.error(`Error fetching user`);
      }
    });
  }, []);

  const handleOnCancel = useCallback(() => {
    controller.abort();
  }, []);

  return <Button onClick={handleOnCancel}>Cancel</Button>;
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

  • Сначала мы создаем новый экземпляр класса AbortController.
  • Затем, в качестве побочного эффекта, мы используем метод get() Axios для получения пользователя из API, предоставляя сигнал AbortController.
  • Наконец, в функции обратного вызова handleOnCancel() мы вызываем метод abort() на экземпляре AbortController, чтобы отменить запрос на выборку.

При использовании функции RxJS fromFetch() нет необходимости подключать сигнал AbortController.
Вместо этого мы можем отменить запрос на выборку, выдав уведомление об ошибке или завершении.

import { Button } from "@mui/material";
import { useCallback, useEffect, useState } from "react";
import { Subject } from "rxjs";
import { fromFetch } from "rxjs/fetch";
import { concatMap, takeUntil, tap } from "rxjs/operators";

export default function App() {
  const [user, setUser] = useState(null);
  const cancel$ = new Subject();

  useEffect(() => {
    const id = 2;
    const subscription = fromFetch(`https://reqres.in/api/users/${id}`)
      .pipe(
        tap((response) => {
          if (!response.ok) {
            throw new Error(response.statusText);
          }
        }),
        concatMap((response) => response.json()),
        tap(user => setUser(user)),
        takeUntil(cancel$)
      )
      .subscribe();
    return () => subscription.unsubscribe();
  }, []);

  const handleOnCancel = useCallback(() => {
    cancel$.next();
  }, []);

  return <Button onClick={handleOnCancel}>Cancel</Button>;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте рассмотрим приведенный выше код:

  • Во-первых, мы используем функцию fromFetch() из RxJS для использования встроенного Fetch API для запроса пользователя. Эта функция возвращает Observable, которая при подписке на нее инициирует запрос.
  • В методе pipe() мы сначала проверяем, не был ли ответ неудачным, и если да, то выдаем уведомление об ошибке statusText.
  • Далее, используя оператор concatMap(), мы объединяем следующее уведомление, которое испускается из Observable, созданного внутри из Promise, возвращенного из метода .json().
  • Далее мы используем оператор takeUntil() для уведомления внешней Observable о завершении и прерывании запроса, если это необходимо, когда субъект cancel$ испускает следующее уведомление.
  • Наконец, в функции обратного вызова handleOnCancel() мы вызываем уведомление next() на субъекте cancel$.

Основные выводы:

  • RxJS предоставляет функции для взаимодействия с родными API Fetch и WebSocket с помощью асинхронных Observables.
  • Оператор fromFetch() использует внутренний AbortController и отменяет запрос, если Observable либо завершается, либо выдается уведомление об ошибке.

Как обрабатывать подписки?

При использовании RxJS лучше всего очистить все подписки в нашем приложении.
Хотя существует несколько различных подходов к обеспечению завершения (или отмены) подписки на Observable, одним из методов является вызов метода .unsubscribe() на экземпляре Subscription, который возвращается из функции subscribe().
Функция teardown, возвращаемая из хука useEffect() — это наша возможность выполнить любую очистку от побочного эффекта.

Дебаунсинг входного потока

В этом примере мы будем управлять потоком search$ Observable, который денонсируется до того, как мы вызовем функцию обратного вызова onSearch(), которая является собственностью компонента.
Хотя мы могли бы просто вызывать функцию обратного вызова onSearch() при каждом изменении входного значения, мы хотим избежать чрезмерных сетевых запросов и перерисовки в браузере.

import CancelIcon from "@mui/icons-material/Cancel";
import SearchIcon from "@mui/icons-material/Search";
import { IconButton } from "@mui/material";
import { useEffect, useMemo, useState } from "react";
import { BehaviorSubject } from "rxjs";
import { debounceTime, tap } from "rxjs/operators";

export default function Search(props) {
  const { onSearch } = props;
  const [search, setSearch] = useState("");
  const search$ = useMemo(() => new BehaviorSubject(""), []);

  useEffect(() => {
    search$.next(search);
  }, [search]);

  useEffect(() => {
    const subscription = search$
      .pipe(debounceTime(1000), tap(onSearch))
      .subscribe();
    return () => subscription.unsubscribe();
  }, []);

  return (
    <div>
      <input
        type="text"
        placeholder="Search"
        onChange={(event) => setSearch(event.target.value)}
        value={search}
      />
      {search$.value && (
        <IconButton onClick={() => setSearch("")}>
          <CancelIcon />
        </IconButton>
      )}
      {!search$.value && <SearchIcon />}
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте рассмотрим приведенный выше код:

  • Мы определили search$ BehaviorSubject с начальным значением пустой строки.
  • Когда состояние search изменяется, метод next() вызывается на субъекте search$ с текущим значением.
  • Мы подписываемся на поток search$ Observable и используем оператор debounceTime() для отмены изменения значения поискового HTMLInputElement. Внутри хука useEffect() мы возвращаем функцию обратного вызова, которая вызовет метод unsubscribe().

Эта реализация подчеркивает использование RxJS для управления потоком данных в нашем приложении из события onChange, вызванного взаимодействием пользователя с поисковым вводом.

Крючок useRxEffect().

Наконец, я хотел бы поделиться простым хуком, который LiveLoveApp использует для наших React-приложений, зависящих от RxJS.
Этот хук позволяет не беспокоиться о подписках.

Давайте посмотрим.

import { useEffect } from 'react';
import { Observable } from 'rxjs';

export function useRxEffect(factory: () => Observable<any>, deps: any[]) {
  useEffect(() => {
    const subscription = factory().subscribe();
    return () => subscription.unsubscribe();
  }, deps);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Хук useRxEffect() намеренно похож на хук useEffect(), предоставляемый React.
Хук ожидает, что функция factory вернет Observable, который отписывается, когда вызывается функция обратного вызова разрушения эффекта.

Вот фрагмент использования хука useRxEffect() на основе предыдущего кода:

import CancelIcon from "@mui/icons-material/Cancel";
import SearchIcon from "@mui/icons-material/Search";
import { IconButton } from "@mui/material";
import { useEffect, useMemo, useState } from "react";
import { BehaviorSubject } from "rxjs";
import { debounceTime, tap } from "rxjs/operators";

export default function Search(props) {
  const { onSearch } = props;
  const [search, setSearch] = useState("");
  const search$ = useMemo(() => new BehaviorSubject(""), []);

  useEffect(() => {
    search$.next(search);
  }, [search]);

  useRxEffect(() => {
    return search$.pipe(debounceTime(1000), tap(onSearch));
  }, []);

  return (
    <div>
      <input
        type="text"
        placeholder="Search"
        onChange={(event) => setSearch(event.target.value)}
        value={search}
      />
      {search$.value && (
        <IconButton onClick={() => setSearch("")}>
          <CancelIcon />
        </IconButton>
      )}
      {!search$.value && <SearchIcon />}
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше примере обратите внимание, что мы заменили хук useEffect() на наш собственный хук useRxEffect() для управления подпиской и отпиской от search$ Observable.

Ключевые выводы

Если вы рассматриваете возможность использования RxJS в существующем или новом приложении React, вот несколько основных выводов, основанных на нашем опыте:

  1. RxJS не является необходимым для создания надежного React-приложения.
  2. RxJS предоставляет функциональную реализацию программирования для создания приложений React с потоками событий, асинхронными данными и многим другим.
  3. RxJS реализует примитив Observable, который совместим с Promises (но без async/await).
  4. RxJS имеет богатую библиотеку функций для создания Observables, преобразования и многоадресной передачи данных, обработки ошибок и многого другого.
  5. RxJS можно рассматривать как lodash для событий.

Оцените статью
devanswers.ru
Добавить комментарий