React Query duomenų sinchronizacija

Kas ta React Query ir kodėl ji tapo tokia populiari

Jei dirbi su React aplikacijomis, tikriausiai jau girdėjai apie React Query arba jos naująjį pavadinimą – TanStack Query. Ši biblioteka pasirodė kaip tikras išgelbėtojas daugeliui frontend kūrėjų, kurie anksčiau turėjo rankiniu būdu tvarkyti visą duomenų gavimo, kaupimo ir sinchronizavimo logiką.

Problema, kurią sprendžia React Query, yra gana paprasta – kaip efektyviai valdyti serverio būseną (server state) React aplikacijoje. Skirtingai nei lokalinė būsena (local state), serverio duomenys yra asinchroniški, gali pasikeisti bet kuriuo metu ir dažnai yra dalijamasi tarp skirtingų komponentų. Tradiciškai kūrėjai naudodavo `useState` ir `useEffect` kombinaciją, bet tai greitai virsta chaotiška logika su daugybe edge case’ų.

React Query iš esmės abstrahuoja visą šią sudėtingą logiką į paprastą API. Ji automatiškai tvarko kešavimą, fono atnaujinimus, duomenų sinchronizaciją tarp skirtingų komponentų ir dar daugybę kitų dalykų, apie kuriuos anksčiau turėjai galvoti pats.

Kaip veikia duomenų sinchronizacija po gaubtu

Vienas iš galingiausių React Query aspektų yra jos duomenų sinchronizavimo mechanizmas. Kai keletas komponentų naudoja tą patį query, biblioteka automatiškai užtikrina, kad visi jie matytų naujausius duomenis. Tai veikia per query key sistemą – kiekvienas query turi unikalų raktą, kuris identifikuoja duomenis.

Pavyzdžiui, jei turite du komponentus, kurie abu naudoja `useQuery([‘users’])`, React Query supras, kad tai tas pats duomenų rinkinys. Kai vienas komponentas inicijuoja duomenų atnaujinimą, kiti automatiškai gauna naujus duomenis. Nereikia jokio papildomo kodo ar sudėtingų state management sprendimų.

const UserList = () => {
  const { data, isLoading } = useQuery(['users'], fetchUsers);
  
  if (isLoading) return 
Kraunasi...
; return (
    {data.map(user =>
  • {user.name}
  • )}
); }; const UserCount = () => { const { data } = useQuery(['users'], fetchUsers); return
Viso vartotojų: {data?.length || 0}
; };

Abu šie komponentai dalijasi tais pačiais duomenimis. Kai React Query atnaujina vartotojų sąrašą, abu komponentai automatiškai persirenderina su naujais duomenimis. Tai veikia net jei komponentai yra visiškai skirtingose aplikacijos vietose.

Invalidation ir automatinis duomenų atnaujinimas

Vienas iš dažniausių scenarijų – jums reikia atnaujinti duomenis po mutacijos (pavyzdžiui, po to, kai vartotojas sukuria naują įrašą). React Query turi elegantišką sprendimą – query invalidation.

Kai naudojate `useMutation`, galite nurodyti, kuriuos query reikia invaliduoti po sėkmingos mutacijos. Invaliduoti query reiškia pažymėti juos kaip pasenusių ir automatiškai inicijuoti jų atnaujinimą, jei jie šiuo metu yra naudojami kokiame nors komponente.

const queryClient = useQueryClient();

const createUserMutation = useMutation(createUser, {
  onSuccess: () => {
    // Invaliduojame vartotojų sąrašą
    queryClient.invalidateQueries(['users']);
  }
});

const handleSubmit = (userData) => {
  createUserMutation.mutate(userData);
  // Po sėkmingo sukūrimo, vartotojų sąrašas automatiškai atsinaujins
};

Šis mechanizmas yra neįtikėtinai galingas, nes leidžia jums išlaikyti UI sinchronizuotą su serveriu be jokio rankinio darbo. Nebereikia rankiniu būdu atnaujinti lokalinės būsenos ar daryti papildomų API iškvietimų.

Optimistiniai atnaujinimai ir UX gerinimas

Kitas įdomus aspektas yra optimistiniai atnaujinimai (optimistic updates). Tai technika, kai UI atnaujinamas iš karto, nebelaukiant serverio atsakymo. Jei serveris grąžina klaidą, pakeitimai yra atšaukiami. Tai sukuria daug greitesnį ir sklandesnį vartotojo patirtį.

React Query leidžia lengvai implementuoti optimistinius atnaujinimus per `onMutate` callback’ą:

const updateUserMutation = useMutation(updateUser, {
  onMutate: async (updatedUser) => {
    // Atšaukiame visus aktyvius query šiam vartotojui
    await queryClient.cancelQueries(['user', updatedUser.id]);
    
    // Išsaugome ankstesnę būseną rollback'ui
    const previousUser = queryClient.getQueryData(['user', updatedUser.id]);
    
    // Optimistiškai atnaujiname UI
    queryClient.setQueryData(['user', updatedUser.id], updatedUser);
    
    return { previousUser };
  },
  onError: (err, updatedUser, context) => {
    // Jei įvyko klaida, grąžiname ankstesnę būseną
    queryClient.setQueryData(['user', updatedUser.id], context.previousUser);
  },
  onSettled: (updatedUser) => {
    // Bet kuriuo atveju, invaliduojame query
    queryClient.invalidateQueries(['user', updatedUser.id]);
  }
});

Taip, kodas atrodo šiek tiek sudėtingesnis, bet rezultatas yra vertas – vartotojas mato pakeitimus akimirksniu, o jei kas nors nepavyksta, viskas grįžta į pradinę būseną.

Fono sinchronizacija ir stale-while-revalidate strategija

React Query naudoja „stale-while-revalidate” strategiją, kuri yra vienas iš pagrindinių jos privalumų. Esmė tokia: kai komponentas užklausia duomenis, biblioteka iš karto grąžina kešuotus duomenis (jei jie egzistuoja), bet tuo pačiu metu fone inicijuoja naują užklausą serveriui.

Tai reiškia, kad vartotojas iš karto mato turinį (net jei jis šiek tiek pasenęs), o po kelių sekundžių automatiškai gauna naujausius duomenis. Nereikia jokių loading spinnerių ar tuščių ekranų.

Galite kontroliuoti, kaip greitai duomenys tampa „pasenusiais” naudodami `staleTime` parametrą:

const { data } = useQuery(['users'], fetchUsers, {
  staleTime: 5 * 60 * 1000, // 5 minutės
  cacheTime: 10 * 60 * 1000, // 10 minučių
});

`staleTime` nurodo, kiek laiko duomenys laikomi šviežiais ir nereikalauja atnaujinimo. `cacheTime` nurodo, kiek laiko neaktyvūs duomenys lieka kešuojami atmintyje. Šie parametrai leidžia tiksliai suderinti balansą tarp našumo ir duomenų aktualumo.

Realaus laiko sinchronizacija su WebSocket

Nors React Query puikiai tinka REST API, kartais reikia realaus laiko duomenų sinchronizacijos. Čia į pagalbą ateina WebSocket integracija. React Query pati savaime nepalaiko WebSocket, bet ją labai lengva integruoti.

Pagrindinis principas – kai gaunate WebSocket pranešimą apie duomenų pasikeitimą, tiesiog invaliduojate atitinkamus query arba rankiniu būdu atnaujiname kešą:

useEffect(() => {
  const ws = new WebSocket('ws://localhost:8080');
  
  ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    
    if (message.type === 'USER_UPDATED') {
      // Invaliduojame konkretų vartotoją
      queryClient.invalidateQueries(['user', message.userId]);
      
      // Arba rankiniu būdu atnaujiname duomenis
      queryClient.setQueryData(['user', message.userId], message.data);
    }
  };
  
  return () => ws.close();
}, [queryClient]);

Tokiu būdu galite turėti geriausią iš abiejų pasaulių – React Query kešavimo ir sinchronizavimo galimybes kartu su realaus laiko atnaujinimais per WebSocket.

Pagination ir infinite scroll su sinchronizacija

Kai dirbate su dideliais duomenų kiekiais, neišvengiamai susidursite su pagination arba infinite scroll poreikiu. React Query turi specialų `useInfiniteQuery` hook’ą, kuris puikiai tinka tokiems scenarijams.

Įdomiausia dalis – kai invaliduojate infinite query, React Query protingai atnaujina visas puslapių grupes, išlaikydamas teisingą duomenų struktūrą:

const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
} = useInfiniteQuery(
  ['users'],
  ({ pageParam = 1 }) => fetchUsers(pageParam),
  {
    getNextPageParam: (lastPage, pages) => {
      return lastPage.hasMore ? pages.length + 1 : undefined;
    },
  }
);

// Kai invaliduojate šį query, visi puslapiai bus atnaujinti
const handleRefresh = () => {
  queryClient.invalidateQueries(['users']);
};

Tai ypač naudinga, kai vartotojas prideda naują įrašą – galite invaliduoti visą sąrašą, ir React Query automatiškai atnaujins visus užkrautus puslapius, išlaikydama scroll poziciją ir bendrą UI būseną.

Kai viskas susideda į vieną didelę paveikslą

React Query duomenų sinchronizacija nėra vienas konkretus feature’as – tai visa ekosistema, kuri dirba kartu. Query invalidation, optimistiniai atnaujinimai, fono sinchronizacija, kešavimas – visi šie mechanizmai veikia harmoningai, sukurdami sklandžią vartotojo patirtį.

Praktiškai, kai kuriate aplikaciją su React Query, turėtumėte galvoti apie duomenų srautus, o ne apie individualius komponentų būsenas. Kiekvienas duomenų tipas turi savo query key, ir viskas, kas naudoja tą patį raktą, automatiškai sinchronizuojasi. Tai kardinaliai keičia požiūrį į state management.

Keletas praktinių patarimų iš patirties: naudokite hierarchinius query keys (pvz., `[‘users’, userId, ‘posts’]`), tai leidžia lengvai invaliduoti susijusius duomenis. Nustatykite protingus `staleTime` ir `cacheTime` parametrus pagal jūsų duomenų pobūdį – dažnai kintantiems duomenims trumpesnius, retai kintiems ilgesnius. Ir nebijokite naudoti optimistinių atnaujinimų – jie iš tiesų pagerina UX, net jei pradžioje atrodo sudėtingi.

Galiausiai, React Query yra ne tik apie techninius sprendimus – ji leidžia jums sutelkti dėmesį į tai, kas iš tiesų svarbu: kurti gerą vartotojo patirtį ir funkcionalumą, o ne kovoti su duomenų sinchronizavimo problemomis.

Daugiau

OVHcloud Europos debesų sprendimai