Cache ou état, essayez React-query

Ciel et mer
Ciel et mer

introduction

Une bibliothèque populaire pour travailler avec l'état des applications Web dans react-js est redux. Cependant, il présente un certain nombre d'inconvénients tels que la verbosité (même en conjonction avec redux-toolkit), la nécessité de sélectionner une couche supplémentaire (redux-thunk, redux-saga, redux-observable). On a le sentiment que d'une manière ou d'une autre c'est trop compliqué et pendant longtemps il y avait des hooks et en particulier le hook useContext .. J'ai donc essayé une autre solution.





Application de test 

« » create react app, typescript, redux-toolkit, redux saga. redux context + react-query. , , , react-query  . .. , .. ,   . .. .





Écrans d'application de test

 

react-query , , .. redux 2 . — , . — , . 





react-context. :





export const CitiesProvider = ({
  children,
}: {
  children: React.ReactNode;
}): JSX.Element => {
  const [citiesState, setCitiesState] = useLocalStorage<CitiesState>(
    'citiesState',
    citiesStateInitValue,
  );

  const addCity = (id: number) => {
    if (citiesState.citiesList.includes(id)) {
      return;
    }
    setCitiesState(
      (state: CitiesState): CitiesState => ({
        ...state,
        citiesList: [...citiesState.citiesList, id],
      }),
    );
  };
 // removeCity..,  setCurrentCity..

  return (
    <itiesContext.Provider
      value={{
        currentCity: citiesState.currentCity,
        cities: citiesState.citiesList,
        addCity,
        removeCity,
        setCurrentCity,
      }}
    >
      {children}
    </itiesContext.Provider>
  );
};

      
      



, setCurrentCity, removeCity



. , localStorage . , , , , .





React-query

, , react-query.   :





import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
 
import { CitiesProvider } from './store/cities/cities-provider';
 
const queryClient = new QueryClient();
 
ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <CitiesProvider>
        <App />
      
      



:





const queryCities = useQuery('cities', fetchCitiesFunc);
const cities = queryCities.data || [];
      
      



'cities'



, . - , Promise, . .





useQuery UseQueryResult



, ,





const { isLoading, isIdle, isError, data, error } = useQuery(..
      
      







export function useCurrentWeather(): WeatherCache {
  const { currentCity } = useContext(itiesContext);
 
 //   
  const queryCities = useQuery('cities', fetchCitiesFunc, {
     refetchOnWindowFocus: false,
    staleTime: 1000 * 60 * 1000,
  });
  const citiesRu = queryCities.data || [];
 
//    .. 
 const city = citiesRu.find((city) => {
    if (city === undefined) return false;
    const { id: elId } = city;
    if (currentCity === elId) return true;
    return false;
  });
 
  const { id: weatherId } = city ?? {};
 
 //   
  const queryWeatherCity = useQuery(
    ['weatherCity', weatherId],
    () => fetchWeatherCityApi(weatherId as number),
    {
      enabled: !!weatherId,
      staleTime: 5 * 60 * 1000,
    },
  );
 
  const { coord } = queryWeatherCity.data ?? {};
 
 //      . 
  const queryForecastCity = useQuery(
    ['forecastCity', coord],
    () => fetchForecastCityApi(coord as Coord),
    {
      enabled: !!coord,
      staleTime: 5 * 60 * 1000,
    },
  );
 
  return {
    city,
    queryWeatherCity,
    queryForecastCity,
  };
}
      
      



staleTime



— , , . , . , staleTime =0



.





 enabled: !!weatherId



, . useQuery



isIdle



. .





const queryWeatherCity = useQuery(['weatherCity', weatherId],.. 
      
      



, , + .





:





export function Forecast(): React.ReactElement {
  const {
    queryForecastCity: { isFetching, isLoading, isIdle, data: forecast },
  } = useCurrentWeather();
 
  if (isIdle) return <LoadingInfo text="   " />;
  if (isLoading) return <LoadingInfo text="  " />;
 
  const { daily = [], alerts = [], hourly = [] } = forecast ?? {};
  const dailyForecastNext = daily.slice(1) || [];
 
  return (
    <>
      <Alerts alerts={alerts} />
      <HourlyForecast hourlyForecast={hourly} />
      <DailyForecast dailyForecast={dailyForecastNext} />
      {isFetching && <LoadingInfo text="  " />}
    </>
  );
}
      
      



isLoading — isFetching - .





React-query . Redux, ( ) 





Fenêtre des outils de développement

, Actions, , , .. , , , . . :





import { ReactQueryDevtools } from 'react-query/devtools';
      
      



, process.env.NODE_ENV === 'production'



,  . Create React App .





react-query , , , .





  • useQueries



    . .. useQuery



    .





const userQueries = useQueries(
  users.map(user => {
    return {
      queryKey: ['user', user.id],
      queryFn: () => fetchUserById(user.id),
    }
  })
      
      



  • , , 3 . retry



    .





  • , , useMutations







const mutation = useMutation(newTodo => axios.post('/todos', newTodo))
      
      



  • , useInfiniteQuery







  • , , , .





Après avoir remplacé redux-toolkit + redux-saga et context + react-query, le code m'a semblé beaucoup plus facile et j'ai obtenu plus de fonctionnalités prêtes à l'emploi pour travailler avec les requêtes au serveur. Cependant, la partie react-context ne dispose pas d'outils de débogage spéciaux et soulève généralement des préoccupations, mais elle s'est avérée assez petite et react-devtools me suffisait. En général, je suis satisfait de la bibliothèque react-query et, en général, l'idée de séparer le cache en une entité distincte me semble intéressante. Mais il s'agit toujours d'une très petite application avec plusieurs demandes d'obtention.   





Liens

La mise en page est correcte uniquement pour les appareils mobiles





Il y a une branche avec redux





Documentation de React-query








All Articles