[Разработка веб-сайтов, JavaScript, ReactJS, TypeScript] Кэш или стэйт, пробуем React-query
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Небо и мореВведение Популярная библиотека для работы с состоянием веб-приложений на react-js это redux. Однако у нее есть ряд недостатков такие как многословность(даже в связке с redux-toolkit), необходимость выбирать дополнительный слой(redux-thunk, redux-saga, redux-observable). Возникает ощущение, что как-то это все слишком сложно и уже давно появились хуки и в частности хук useContext.. Так что я попробовал другое решение.Приложение для теста У меня было простое веб приложение «Прогноз погоды» написанное с помощью create react app, typescript, redux-toolkit, redux saga. Потом я заменил весь redux на context + react-query. Это очень маленькое, однако рабочее приложение, которым я сам пользуюсь, позволило мне использовать react-query для описания уже существующей логики. Т.е. не делать абстрактный нерабочий проект, который просто раскрывает базовые возможности библиотеки.. В приложении есть выбор городов, получение текущей погоды и прогноза. Т.е. максимум три последовательных запроса к серверу.
Скрины тестового приложенияНовый стэйт Библиотека 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, но появляется в виде фиксированного окошка поверх приложения(можно закрыть и останется только кнопка)
Окно инструментов разработчикаЕсть информация о состоянии каждого запроса, также есть кнопки 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
- Также есть предзагрузка, инвалидация запросов, оптимистичное обновление и еще много всего, что можно посмотреть в документации.
Заключение После замены redux-toolkit + redux-saga и context + react-query код мне показался значительно проще и я получил из коробки больший функционал для работы с запросами к серверу. Однако часть с react-context не имеет специальных инструментов отладки и вообще вызывает опасения, но она оказалось совсем небольшой и мне вполне хватило react-devtools. В целом я доволен библиотекой react-query и вообще идея отделения кэша в отдельную сущность кажется мне интересной. Но все же это очень маленькое приложение с несколькими get запросами.. СсылкиВерстка корректна только для мобильных устройствЕсть ветка с reduxДокументация react-query
===========
Источник:
habr.com
===========
Похожие новости:
- [Высокая производительность, Разработка веб-сайтов, PHP, Magento] PHP-SPX простой профайлер трейсер для PHP
- [Разработка веб-сайтов, CSS, Программирование] Инструменты для аудита CSS (перевод)
- [Программирование, Анализ и проектирование систем, Проектирование и рефакторинг, Управление разработкой, TypeScript] Чем меня не устраивает гексагональная архитектура. Моя имплементация DDD – многоуровневая блочная архитектура
- [JavaScript, Космонавтика, Лайфхаки для гиков] Отслеживание и визуализация положения МКС с помощью 30 строк JavaScript-кода (перевод)
- [Разработка веб-сайтов, JavaScript, HTML] Шпаргалка по JS-методам для работы с DOM
- [Разработка веб-сайтов, CSS] CSS – строго типизированный язык программирования (перевод)
- [Разработка веб-сайтов, PHP, Symfony, Конференции] Прямой эфир про тесты, трейты, devops в монолите, переход на Go и KPHP с казанского PHP-митапа
- [JavaScript, Node.JS] Хочу middleware, но не хочу ExpressJS
- [Разработка веб-сайтов, Карьера в IT-индустрии] От джуниора до сениора: как это было у меня
- [Разработка веб-сайтов, Python, Flask] Оно живое! Вышла версия Flask 2.0
Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_javascript, #_reactjs, #_typescript, #_react, #_reactquery, #_upravlenie_sostojaniem (управление состоянием), #_razrabotka_vebsajtov (
Разработка веб-сайтов
), #_javascript, #_reactjs, #_typescript
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:51
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Небо и мореВведение Популярная библиотека для работы с состоянием веб-приложений на react-js это redux. Однако у нее есть ряд недостатков такие как многословность(даже в связке с redux-toolkit), необходимость выбирать дополнительный слой(redux-thunk, redux-saga, redux-observable). Возникает ощущение, что как-то это все слишком сложно и уже давно появились хуки и в частности хук useContext.. Так что я попробовал другое решение.Приложение для теста У меня было простое веб приложение «Прогноз погоды» написанное с помощью create react app, typescript, redux-toolkit, redux saga. Потом я заменил весь redux на context + react-query. Это очень маленькое, однако рабочее приложение, которым я сам пользуюсь, позволило мне использовать react-query для описания уже существующей логики. Т.е. не делать абстрактный нерабочий проект, который просто раскрывает базовые возможности библиотеки.. В приложении есть выбор городов, получение текущей погоды и прогноза. Т.е. максимум три последовательных запроса к серверу. Скрины тестового приложенияНовый стэйт Библиотека 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> ); }; 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 || []; 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, }; } 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="Обновляется дневной прогноз" />} </> ); } Окно инструментов разработчикаЕсть информация о состоянии каждого запроса, также есть кнопки Actions, можно вручную производить перезапрос, очистку, удаление.. Если учитывать, что библиотека никак не модифицирует полученные данные, то многое можно увидеть и просто в инструментах разработчика браузера, в разделе сеть. Но все же эти инструменты существенно расширяют возможности отладки. Подключаются они в одну строчку: import { ReactQueryDevtools } from 'react-query/devtools';
const userQueries = useQueries(
users.map(user => { return { queryKey: ['user', user.id], queryFn: () => fetchUserById(user.id), } })
const mutation = useMutation(newTodo => axios.post('/todos', newTodo))
=========== Источник: habr.com =========== Похожие новости:
Разработка веб-сайтов ), #_javascript, #_reactjs, #_typescript |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:51
Часовой пояс: UTC + 5