Cache ou estado, tente React-query

Céu e mar
Céu e mar

Introdução

Uma biblioteca popular para trabalhar com o estado de aplicativos da web em react-js é redux. No entanto, ele tem uma série de desvantagens, como verbosidade (mesmo em conjunto com o redux-toolkit), a necessidade de selecionar uma camada adicional (redux-thunk, redux-saga, redux-observable). Há uma sensação de que de alguma forma isso é muito complicado e por muito tempo existiam ganchos e, em particular, o gancho useContext. Então, tentei outra solução.





Aplicação de teste 

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





Telas do aplicativo de teste

 

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





Janela de ferramentas do desenvolvedor

, 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







  • , , , .





Depois de substituir redux-toolkit + redux-saga e context + react-query, o código parecia muito mais fácil para mim e obtive mais funcionalidades prontas para trabalhar com solicitações ao servidor. No entanto, a parte do contexto de reação não tem ferramentas especiais de depuração e geralmente levanta preocupações, mas acabou sendo bem pequena e as ferramentas de reação foram suficientes para mim. Em geral, estou satisfeito com a biblioteca de consulta de reação e, em geral, a ideia de separar o cache em uma entidade separada parece interessante para mim. Mas, ainda assim, este é um aplicativo muito pequeno com várias solicitações get.   





Links

O layout está correto apenas para dispositivos móveis





Existe uma filial com redux





Documentação de consulta do React








All Articles