Posty

Czym jest react-query i czy może zastąpić bibliotekę stanu?

React Query

Czym jest react-query?

React Query jest przez autorów nazywany „brakującą biblioteką do fetchowania danych, która to czyni problemy cache’owania, pobierania i synchronizowania danych czymś trywialnym, bardzo prostym”. Dane, o których mowa, płyną pomiędzy przeglądarką, czyli klientem a serwerem(a w zasadzie aplikacją osadzoną na serwerze).

Jakie problemy rozwiązuje react-query?

Jak pewnie dobrze wiecie, React.js sam w sobie jest tylko biblioteką widoku. Jest narzędziem, które pomaga nam budować interfejsy wizualne aplikacji. Od samego początku powstania React’a społeczność borykała się z problem okiełznania warstwy danych po stronie klienta (przeglądarki). Przed powstaniem bibliotek typu SPA jak React czy Vue problem ten nie istniał. Dotychczasowe rozwiązania bazujące na architekturze MVC (Model View Controler) np. Ruby on Rails czy Django były kompletnymi narzędziami do budowania aplikacji webowych. Dane do warstwy widoku płynęły bezpośrednio z kontrolerów i na tym koniec.

W momencie pojawienia się bibliotek SPA (Single Page Application) dane do warstwy widoku najczęściej pochodziły z zewnętrznego źródła, czyli np. aplikacji serwerowej napisanej w node.js. Aby do takich danych się dostać, potrzebowaliśmy wykonać asynchroniczny request do endpointu, który to zwracał obiekt typu JSON. Niestety, tutaj pojawiały się pytania:

  • gdzie te dane trzymać, zanim je wy-renderujemy?
  • kiedy i jak synchronizować te dane z tymi, które “żyją” w tym samym czasie w aplikacji serwerowej?

Odpowiedzią na te pytania w początkowej fazie była architektura Flux wprowadzona przez twórców Reacta, następnie jej pochodna, czyli Redux i biblioteki alternatywne jak np. Mobx. Rozwiązywały one podstawowe problemy związane z warstwą danych po stronie przeglądarki, ale nie rozwiązywały one rzeczy takich jak:

  • asynchroniczna synchronizacja danych,
  • cache’owanie,
  • powielanie zapytań do serwera,
  • zarządzanie pamięcią i sprzątanie śmieci (garbage collector),
  • niepożądane mutacje danych,
  • “świadomość”, kiedy dane po stronie klienta nie są tożsame z tymi na serwerze.

Problemy te powodowały i nadal powodują ból głowy u wielu programistów. Powiem szczerze, że coś o tym wiem, ponieważ po ostatnim projekcie komercyjnym, w którym tworzyliśmy dość skomplikowany moduł do wyszukiwania nieruchomości i używaliśmy do tego Mobx’a - miałem ochotę pójść na długi urlop i jak najszybciej o tym zapomnieć.

Okej, a więc co daje mi react-query?

React Query, “out of the box” rozwiązuje problemy związane z cache’owaniem, pobieraniem i synchronizowaniem danych pomiędzy naszą aplikacją kliencką a serwerową. W dużym skrócie: zamiast tworzyć cały ten boilerplate związany z budowaniem globalnego stanu przy użyciu Reduxa czy Mobx’a, setup’owaniu struktury projektu, zastanawianiu się gdzie umieścić pliki związane z warstwą danych, jaką konwencje przyjąć, w przypadku react-query wystarczy, że nasz “entry point” - czyli np. plik index.js, owrappujemy w komponent <QueryClinetProvider />. Na tym kończy się konfiguracja warstwy danych w naszym projekcie. Zdziwieni? Ja też byłem!

Dodam, iż biblioteki takie, jak react-query przypominają nam trochę tradycyjną architekturę aplikacji MVC, gdzie warstwa widoku miała bezpośredni dostęp do danych (do stanu serwera) poprzez kontroler. Takim kontrolerem w przypadku aplikacji SPA może stać się biblioteka react-query.

Aby wam to zobrazować posłużę się snippetem kodu z oficjalnej dokumentacji. Zwróćcie uwagę na komentarze w kodzie wyjaśniające przykład.

import { QueryClient, QueryClientProvider, useQuery } from "react-query";

const queryClient = new QueryClient();

// w "entry point'cie" naszej aplikacji wrappujemy wszystkie komponenty
// w Providera, który od teraz będzie dostarczał nam globalny stan w kontekście
// całej aplikacji
export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  );
}

// w przykładowym komponencie używamy hooka useQuery, aby pobrać dane.
// Do pobrania danych w poniższym przykładzie użyto defaultowej funkcji
// fetch
function Example() {
  // useQuery zwraca nam masę gotowych zmiennyc, które możemy użyć np.
  // do warunkowego renderowania loadera lub treści właściwej komponentu
  const { isLoading, error, data } = useQuery("repoData", () =>
    fetch(
      "https://api.github.com/repos/tannerlinsley/react-query"
    ).then((res) => res.json())
  );

  // wyświetlamy loader jeśli dane się ładują
  if (isLoading) return "Loading...";

  // renderujemy błąd w przypadku wystąpienia błędu
  if (error) return "An error has occurred: " + error.message;

  // renderujemy treść właściwą komponentu
  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
      <strong>👀 {data.subscribers_count}</strong>{" "}
      <strong>{data.stargazers_count}</strong>{" "}
      <strong>🍴 {data.forks_count}</strong>
    </div>
  );
}

Prawda, że banalnie proste?

Czy react-query może zastąpić bibliotekę stanu?

Jak to zwykle w świecie programowania bywa, odpowiedź brzmi — to zależy.

Dla większości aplikacji rezygnacja z bibliotek stanu i zastąpienie ich biblioteką react-query jest możliwa. Logika biznesowa reprezentowana do tej pory poprzez Redux’a czy Mobx’a zostaje zastąpiona poprzez proste zapytania do warstwy danych, która już “żyje” na serwerze. Pozwoli nam to usunąć nieco “bolierplate’owego” kodu związanego z utrzymaniem client-state i zastąpić go krótszym i bardziej eleganckim kodem react-query.

W przypadku aplikacji gdzie użytkownik manipuluje danymi w sposób synchroniczny, czyli np. aplikacje takie jak “webowy Paint” czy aplikacja do miksowania muzyki, client-state w postaci biblioteki stanu wciąż będzie lepszym i słusznym rozwiązaniem aniżeli react-query. Przyznacie bowiem, że w przypadku “webowego Painta” i użycia react-query, narysowanie prostej kreski wiązałoby się z ciągłym asynchronicznym aktualizowaniem danych na serwerze. Skutkowałoby to setkami asynchronicznych zapytań do serwera — bardzo nieoptymalne rozwiązanie.

PRO TIP:

Powinniśmy mieć nadal na uwadze, że gdy kod związany z react-query rozrasta się i powoduje, że nasze komponenty mają ponad 100-150 linijek winno się rozważyć ekstrakcję kodu związanego z react-query do osobnych plików umieszczonych w dedykowanym do tego katalogu np. ~/react-query/. W ten sposób osiągniemy coś, co programiści nazywają tajemniczo: “wyciąganie logiki biznesowej do wyższej warstwy abstrakcji”. A znaczy to tyle co:

  1. wytnij kawałek kodu, który używasz w module X (np. komponencie),
  2. umieść go w katalogu Z, skąd będzie on mógł być re-użyty w innych “specyficznych” modułach budowanej aplikacji.

Jakie są alternatywy dla React Query??

Alternatywą dla biblioteki react-query jest SWR przygotowane przez twórców Next.js. Jako że ten post dotyczy React Query, nie będę się rozpisywał na temat SWR — zachęcam Was natomiast do odwiedzenia oficjalnej dokumentacji SWR oraz artykuły na blogu LogRocket, który świetnie wyjaśnia podobieństwa i różnice pomiędzy tymi technologiami. Dokumentacja SWR, SWR kontra react-query

Podsumowanie

React Query jest biblioteką która zmienia podejście do zarządzania danymi w aplikacjach budowanych w oparciu o biblioteki SPA (React, Vue). Zastępuje ona logikę biznesową związaną z zarządzaniem danymi po stronie klienta (contexts, reducers, middlewares, action creators etc.), kilkoma prostymi “queries” pozwalając programistom uzyskać ten sam efekt w krótszym czasie i bez frustracji. Rozwiązuje ona problemy związane z cache’owaniem, powielaniem zapytań do serwera, zarządzeniem pamięcią oraz potencjalnymi “memory leaks”. Oprócz tego współpracuje z TypeScript, a zapytania mogą być wykonywane przy użyciu Promise’ów, zapytań GraphQL czy tradycyjnych endpoint’ów do REST API. Alternatywami dla React Query są SWR lub Apollo.