Większość kodu frontendowego traktuje pracę asynchroniczną jak jednolity kształt. Uruchamiasz Promise, czekasz na jego rozwiązanie, wrzucasz wynik do lokalnego stanu i pozwalasz frameworkowi rozliczyć różnice. Ten wzorzec jest kuszący, ponieważ sprawdza się wszędzie: przy wywołaniu REST, wysyłaniu formularza czy wiadomości WebSocket. Wszystkie trafiają do tego samego useEffect lub obsługi zdarzeń, przechodzą przez setState i wyglądają jak identyczna asynchroniczna mechanika. Ta jednolitość to pułapka. W prawdziwej aplikacji nie cała praca asynchroniczna jest taka sama. Udawanie, że jest inaczej, zmienia Twoje komponenty UI w przypadkowych architektów danych, sklejanych za pomocą hooków useEffect i nadziei na szczęście.
Operacje asynchroniczne dzielą się w rzeczywistości na trzy różne gatunki. Każdy z nich ma inną relację z czasem, buforowaniem i własnością. Umiejętność ich rozróżnienia to klucz do utrzymania frontendu w szybkim, poprawnym i zdrowym stanie.
Zapytania: Fakty z adresem
Zapytanie to nie tylko pobranie danych (fetch). To prośba o identyfikowalny fakt. Prosisz o /user/123, a nie o „jakieś dane użytkownika”. To rozróżnienie ma znaczenie, ponieważ to tożsamość umożliwia buforowanie. Jeśli dwa komponenty na tym samym ekranie potrzebują tego samego rekordu użytkownika, powinny współdzielić jedną odpowiedź. Gdy każdy komponent przechowuje własną lokalną kopię w useState, fragmentujesz swoją prawdę. Awatar w nagłówku i nazwa w pasku bocznym rozjeżdżają się, ponieważ zostały pobrane w różnych odstępach czasu lub jeden proces zakończył się błędem, podczas gdy drugi powiódł się.
Myśl o zapytaniu jak o zasobie, a nie o akcji. Posiada klucz cache'u, politykę świeżości i cykl życia, który wykracza poza pojedynczy komponent. Dobrze zbudowana warstwa zapytań rozumie, że odczyt /projects?page=2 różni się od odczytu /projects?page=3. Każdy zestaw adresów URL i parametrów tworzy adres, a dane pod tym adresem mogą być nieaktualne, świeże lub nieobecne. Interfejs użytkownika nie powinien zajmować się tą księgowością. Powinien poprosić warstwę danych o user:123 i otrzymać migawkę (snapshot). To, czy ta migawka pochodzi z serwera sprzed dwóch sekund, czy z cache'u sprzed dwóch milisekund, nie leży w kompetencjach komponentu.
Praktyczne konsekwencje są natychmiastowe. Traktując każdy odczyt jako imperatywne pobieranie wewnątrz komponentu, tracisz możliwość deduplikacji. Tracisz odświeżanie w tle. Tracisz możliwość natychmiastowego wyświetlenia danych z cache'u przy jednoczesnej ich walidacji w tle. Zapytanie zasługuje na miejsce poza drzewem UI.
Mutacje: Zmiana świata
Jeśli zapytania pytają o świat, mutacje go zmieniają. Samo żądanie sieciowe — POST, PUT lub DELETE — to zazwyczaj najłatwiejsza część. Trudniejsza jest cała reszta, która dzieje się po tym, jak serwer odpowie „OK”.
Załóżmy, że użytkownik aktualizuje swoją nazwę wyświetlaną. Sama mutacja to pojedyncze żądanie. Ale jej promień rażenia obejmuje wszystko. Strona profilu przechowuje starą nazwę. Pasek nawigacji pokazuje starą nazwę. Historia komentarzy może się na nią powoływać. Jeśli Twój kod mutacji robi nic poza przełączeniem lokalnej flagi isLoading i aktualizacją jednego elementu stanu, Twoja aplikacja zaczyna kłamać samej sobie. Niektóre fragmenty UI udają, że zmiana nastąpiła. Inne nie mają pojęcia, że cokolwiek się zmieniło.
Mutacja musi zadeklarować swój wpływ na graf danych. Powinna informować system, które zapytania są teraz nieaktualne, które klucze cache'u wymagają ponownego pobrania i które relacje uległy zmianie. To zasadniczo różni się od zapytania. Zapytanie jest tylko do odczytu i może być współdzielone. Mutacja skupia się na zapisie i jest destrukcyjna dla istniejących cache'ów. Sprowadzenie ich do tej samej abstrakcji sprawia, że programiści kończą, ręcznie wywołując refetch() w przypadkowych komponentach, lub co gorsza, rozrzucając hooki useEffect po całym drzewie, aby „zsynchronizować” stan lokalny z serwerem.
Model własności również jest inny. Zapytania są zazwyczaj własnością cache'u. Mutacja należy do akcji użytkownika, która ją wywołała. Posiada stan oczekiwania, stan błędu i potencjalnie wartość opt
