Caché o estado, intente React-query

Cielo y mar
Cielo y mar

Introducción

Una biblioteca popular para trabajar con el estado de las aplicaciones web en react-js es redux. Sin embargo, tiene una serie de desventajas como la verbosidad (incluso junto con redux-toolkit), la necesidad de seleccionar una capa adicional (redux-thunk, redux-saga, redux-observable). Existe la sensación de que, de alguna manera, todo esto es demasiado complicado y durante mucho tiempo hubo ganchos y, en particular, el gancho useContext. Así que probé otra solución.





Aplicación de prueba 

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





Probar pantallas de aplicación

 

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, ( ) 





Ventana de herramientas para desarrolladores

, 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







  • , , , .





Después de reemplazar redux-toolkit + redux-saga y context + react-query, el código me pareció mucho más fácil y obtuve más funcionalidad para trabajar con solicitudes al servidor. Sin embargo, la parte de contexto de reacción no tiene herramientas de depuración especiales y generalmente genera preocupaciones, pero resultó ser bastante pequeña y reaccionar-devtools fue suficiente para mí. En general, estoy satisfecho con la biblioteca react-query y, en general, la idea de separar el caché en una entidad separada me parece interesante. Pero aún así, esta es una aplicación muy pequeña con varias solicitudes de obtención.   





Enlaces

El diseño es correcto solo para dispositivos móviles





Hay una rama con redux.





Documentación de React-query








All Articles