Jak React Query skrócił 500 linii w warstwie API aplikacji React Native
Przykład z rzeczywistego świata dotyczący React Native, który pokazuje, jak przejście z ręcznego pobierania danych za pomocą useEffect na React Query pozwoliło usunąć powtarzający się kod oraz poprawić funkcjonowanie cache’owania i obsługę pracy offline.
Wprowadzenie
Jakieś czas temu kodbase React Native, który może być panu znany, napotkał podobny problem.
Prawie każdy ekran wymagający danych zdalnych stosował ten sam schemat:
- Zawieranie wywołań do pobierania danych wewnątrz
useEffect - Kilka oddzielnych flag oznaczających stan ładowania
- Sprawdzone bloki obsługi błędów
- Ręcznie napisana logika odświeżania treści
- Mechanizmy ręcznego ponawiania prób
- Tymczasowe rozwiązania dotyczące lokalnego przechowywania danych
Funkcjonalnie nic z tego nie było uszkodzone. Jednak utrzymanie spójności w dziesiątkach ekranów stało się prawdziwym obciążeniem pod względem konserwacji.
Przejście na React Query (od TanStack) pozwoliło zespołowi usunąć dużą ilość powtarzalnego kodu sieciowego, uzyskując w zamian lepsze zarządzanie pamięcią cache, bardziej przejrzyste sterowanie procesem ładowania oraz bardziej niezawodne działanie w trybie offline. Biblioteka oferuje wbudowane cacheowanie zapytań, automatyczne ponowne pobieranie danych w tle oraz logikę uwzględniającą warunki sieciowe, co eliminuje potrzebę ręcznego pisania takiej mechaniki zarządzania stanem.
Poniżej przedstawiono porównanie starego, ręcznego podejścia z wersją wykorzystującą React Query, na podstawie wzorów z rzeczywistych ekranów w React Native używanych w produkcji.
Problemy z tradycyjnym zarządzaniem API
Konfiguracja typowego ekranu wyglądała mniej więcej w ten sposób:
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
const response = await api.getPosts();
setData(response);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
To samo szablon pojawiał się wielokrotnie w całym aplikacji.
Każdy ekran musiał samodzielnie zarządzać:
- Flagą ładowania
- Flagą błędu
- Obsługą ponownych prób
W miarę rozwoju aplikacji to powtarzające się wzorce stawały się coraz trudniejsze do utrzymania w spójności.
Pojawia się React Query
To samo interfejs, przepisane przy użyciu React Query, sprowadza się do tego:
const { data, isLoading, error, refetch } = useQuery({
queryKey: ["posts"],
queryFn: fetchPosts,
});
To jest cała implementacja.
React Query automatycznie zajmuje się wszystkim poniższym:
- Wskazówki ładowania
- Stany błędów
- Usuwanie identycznych zapytań w trakcie przetwarzania
- Ponowne pobieranie danych w tle
- Zarządzanie cache’em
- Ponawianie nieudanych zapytań
- Reagowanie na ponowne połączenie sieciowe
Większość tych funkcji jest dostępna od razu, a ich ustawienia można dostosować za pomocą parametrów takich jak staleTime, gcTime, konfiguracja ponawiania prób oraz ustawienia odświeżania danych.
Porównanie nr 1: Zapytania sieciowe
Przed React Query
Załóżmy trzy oddzielne ekrany, które wszystkie potrzebują tych samych danych profilu użytkownika.
Bез żadnej wspólnej warstwy cache’owania, każdy z nich wysyła własne zapytanie:
Profile Screen → API Call
Settings Screen → API Call
Dashboard Screen → API Call
Wynik: trzy oddzielne żądania sieciowe do tych samych danych.
Z React Query
Profile Screen → API Call
Settings Screen → Cached Data
Dashboard Screen → Cached Data
Tym razem wynik: tylko jedno żądanie sieciowe.
React Query przechowuje wyniki pod kluczem zapytania i udostępnia te dane z cache’u wszystkim komponentom, które o nie proszą. Każdy ekran, który później poprosi o ten sam klucz, otrzymuje wartość z cache’u natychmiast, a tło może w tle aktualizować te dane.
Wynik w produkcji
Na ekranach, które użytkownicy często odwiedzają, oznacza to:
- Znacznie mniej duplikatowych żądań do API
- Mniejsze obciążenie serwerów backendowych
- Szybsze przejścia między ekranami
Porównanie #2: Efektywność buforowania
Buferowanie to chyba obszar, w którym React Query przynosi największe korzyści.
Gdy ta sama zapytanie jest ponownie wysyłane, zanim jego zbuforowana kopia stanie się przestarzała:
useQuery({
queryKey: ["products"],
queryFn: getProducts,
staleTime: 300000,
});
Interfejs może natychmiast pokazać dane z bufora, przy czym React Query opcjonalnie odświeża je cicho w tle. Zapisy z bufora są przechowywane i ostatecznie usuwane według ustawień, które sami określamy.
Rzeczywisty przykład
Rozważmy ekran listy produktów w aplikacji e-commerce.
Bez buforowania otwarcie tego ekranu oznacza:
Open Products
↓
Network Request
↓
Navigate Back
↓
Open Products Again
↓
Network Request
Z użyciem React Query ten sam proces wygląda tak:
Open Products
↓
Network Request
↓
Navigate Back
↓
Open Products Again
↓
Instant Cached Data
Różnica polega na tym, że aplikacja wydaje się znacznie szybsza dla osoby, która ją używa.
Porównanie #3: Stany ładowania
Zanim zaczęto używać React Query, śledzenie stanu ładowania wymagało obsługi kilku wartości logicznych:
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [isRetrying, setIsRetrying] = useState(false);
Następnie jedna wywołanie hooka ujawnia wszystko, co jest potrzebne:
const {
isLoading,
isFetching,
isRefetching,
} = useQuery(...)
React Query rozróżnia pierwsze załadowanie od wszelkich późniejszych żądań w tle, co znacznie upraszcza logikę interfejsu użytkownika związaną z nimi.
Prawdziwa korzyść
Zamiast pokazywać spinner na całym ekranie przy każdym żądaniu, aplikacja może odróżniać:
- Pierwotne załadowanie: spinner na całym ekranie
- Odświeżenie w tle: mały, niewidoczny spinner
- Dane już zlokalizowane w pamięci: brak żadnej widocznej przerwy
Dzięki temu aplikacja wydaje się znacznie bardziej responsywna dla użytkownika.
Porównanie nr 4: Obsługa offline
Zachowanie aplikacji w trybie offline to jedna z kwestii, które zespoły mają tendencję do lekceważenia.
Ręczne radzenie sobie z tym zwykle wygląda w ten sposób:
Check Connectivity
Pause Requests
Retry Later
Handle Errors
Refetch On Reconnect
To oznacza konieczność stosowania specjalistycznej logiki łączności rozrzuconej po całym kodzie źródłowym.
React Query oferuje natywną obsługę pracy online i offline, wraz z funkcją ponownego pobierania danych reagującą na wydarzenia ponownego połączenia. W React Native można to skonfigurować za pomocą onlineManager w połączeniu z wsłuchaczami stanu sieci platformy.
Na przykład:
onlineManager.setEventListener(...)
React Query można również skonfigurować do pracy w trybie offline-first, dostosowując w związku z tym jego zachowanie sieciowe.
Prawdziwy przykład
Weźmy jako przykład aplikację do czytania wiadomości.
W ciągu dnia połączenie użytkownika może wyglądać w następujący sposób:
- Rano: online
- Po południu: offline
- Wieczorem: ponownie online
Przez cały ten okres artykuły wcześniej zapisane w pamięci pozostają dostępne do odczytu, a po przywróceniu połączenia nowe treści mogą zostać automatycznie zsynchronizowane.
Zastosowania w praktyce
1. Aplikacje informacyjne
Co to przynosi:
- Artykuły w pamięci cache
- Automaticzne odświeżanie tła
- Zmniejszone ogólne obciążenie API
- Możliwość dalszego czytania w trybie offline
2. Aplikacje e-commerce
Co to zapewnia:
- Listy produktów w pamięci cache
- Zgromadzanie kategorii z wyprzedzeniem
- Szybsze przemieszczanie się między sekcjami
- Zauważalnie płynniejsze doświadczenie zakupowe
React Query umożliwia również wcześniejsze pobieranie danych przed dokonaniem nawigacji, co skraca czas oczekiwania.
3. Aplikacje z panelami sterowania
Co to zapewnia:
- Widgety, które automatycznie się odświeżają
- Pamięć cache udostępniana na wielu ekranach
- Zmniejszony ruch sieciowy
- Znacznie prostsze zarządzanie stanem
Taki model doskonale pasuje do paneli analitycznych i paneli administracyjnych.
Co faktycznie usunęliśmy
Gdy migracja została zakończona:
Usunięte
- Samodzielnie opracowana obsługa stanu ładowania
- Ręczny kod ponawiania prób
- Duplikowane wywołania API
- Łącze szablonowe do odświeżania treści
- Samodzielna logika cache’owania
- Ręczne zarządzanie ponownym pobieraniem danych
Dodane
- Sama biblioteka React Query
- Klucze zapytań
- Instancja
QueryClient
Ostateczny efekt: nie było już potrzeby około 500 linii kodu do obsługi API.
Kiedy React Query może nie być konieczny
Użycie React Query może być przesadą, jeśli:
- Plik aplikacji wykonywa tylko kilka wywołań API
- Dane w tle prawie się nie zmieniają
- Cache’owanie rzeczywiście nie jest potrzebne
- Bezpośrednie działanie aplikacji offline nie ma znaczenia w danym przypadku użycia
Dla większości aplikacji produkcyjnych korzyści z jego użycia zazwyczaj szybko przewyższają początkowy koszt nauki.
Podsumowanie
React Query to coś więcej niż tylko kolejny sposób na pobieranie danych.
Funkcjonuje jako kompletna platforma do zarządzania stanem serwera, eliminując powtarzalny kod API i jednocześnie poprawiając funkcjonowanie buforowania, zachowanie podczas ładowania, efektywność sieciową oraz doświadczenie użytkowników w trybie offline.
Największą korzyścią nie była sama wydajność.
Było to zmniejszenie złożoności.
Mniej kodu. Mniej błędów. Lepsze doświadczenie dla osób korzystających z aplikacji.
To połączenie sprawiło, że migracja była opłacalna.
Pozycje pokrewne
- Gdzie kryje się opóźnienie w React Native: przekraczanie granic, a nie slow code — Dowiedz się, dlaczego szybkie API i komponenty z mechanizmem memoization mogą nadal wydawać się powolne w React Native, oraz jak batchowanie, throttling i projektowanie z wykorzystaniem JSI zmniejszają koszty działania aplikacji.
- Od Bridge do JSI: jak zdecydować o przejściu na nową architekturę React Native — W jaki sposób JSI, Fabric i TurboModules zastępują stary most komunikacyjny React Native, co oznaczają uzyskane korzyści w praktyce oraz jak bezpiecznie przeprowadzić audyt i migrację istniejącej aplikacji.