React Query¶
React Query - библиотека для получения, кэширования, синхронизации и обновления "серверного" состояния в React-приложениях.
Установка¶
1 2 3 | |
Пример¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
Быстрый старт¶
Ключевыми концепциями React Query являются:
- Запросы (queries)
- Мутации (mutations)
- Инвалидация (аннулирование, признание недействительным) запроса (query invalidation), его кэша
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
Инструменты разработчика¶
По умолчанию инструменты разработчика не включаются в производственную сборку (когда process.env.NODE_ENV === 'production').
Существует два режима добавления инструментов в приложение: плавающий (floating) и встроенный (inline).
Плавающий режим¶
1 2 3 4 5 6 7 8 | |
Настройки¶
initialIsOpen- еслиtrue, то панель инструментов по умолчанию будет открытаpanelProps- используется для добавления пропов к панели, например,className,styleи т.д.closeButtonProps- используется для добавления пропов к кнопке закрытия панелиtoggleButtonProps- используется для добавления пропов к кнопке переключенияposition: "top-left" | "top-right" | "bottom-left" | "bottom-right" - положение логотипаReact Queryдля открытия/закрытия панели
Встроенный режим¶
1 2 3 4 5 6 7 8 9 10 11 | |
Важные настройки по умолчанию¶
- Экземпляры запроса(query instances), созданные с помощью
useQueryилиuseInfiniteQueryпо умолчанию считают кэшированные данные устаревшими (stale) - Устаревшие запросы автоматически повторно выполняются в фоновом режиме (background) в следующих случаях:
- Монтирование нового экземпляра запроса
- Переключения окна в браузере
- Повторное подключение к сети
- Когда задан интервал выполнения повторного запроса (refetch interval)
- Результаты запроса, не имеющие активных экземпляров
useQuery,useInfiniteQueryили наблюдателей (observers) помечаются как "неактивные" (inactive) и остаются в кэше - По умолчанию запросы, помеченные как неактивные, уничтожаются сборщиком мусора через 5 минут
- Провалившиеся запросы автоматически повторно выполняются 3 раза с экспоненциально увеличивающейся задержкой перед передачей ошибки в UI
- Результаты запросов по умолчанию структурно распределяются для определения того, действительно ли данные изменились, и если это не так, ссылка на данные остается неизменной, что способствует стабилизации данных при использовании
useMemoиuseCallback
Запросы¶
Основы¶
Запрос - это декларативная зависимость асинхронного источника данных, связанного с уникальным ключом. Запрос может использоваться с любым основанным на промисе методом (включая методы GET и POST) получения данных от сервера. Если метод изменяет данные на сервере, то лучше использовать мутации.
Для подписки на запрос в компоненте или пользовательском хуке следует вызвать хук useQuery со следующими параметрами:
- Уникальный ключ запроса
- Функция, возвращающая промис, который
- разрешается данными (resolve data) или
- выбрасывает исключение (throw error)
1 2 3 4 5 | |
Уникальный ключ используется для выполнения повторных запросов, кэширования и распределения запросов в приложении.
Результаты запроса, возвращаемые useQuery, содержат всю необходимую информацию о запросе.
1 | |
Объект result содержит несколько важных состояний (states). Запрос может находиться в одном из следующих состояний:
isLoadingилиstatus === 'loading'- запрос находится на стадии выполнения, данные еще не полученыisErrorилиstatus === 'error'- запрос завершился ошибкойisSuccessилиstatus === 'success'- запрос завершился успешно, данные доступныisIdleилиstatus === 'idle'- запрос отключен
Кроме того, объект result содержит следующую информацию:
error- если запрос находится в состоянииisError, ошибка доступна через свойствоerrordata- если запрос находится в состоянииsuccess, данные доступны через свойствоdataisFetching- в любом состоянии, если запрос находится на стадии выполнения (включая фоновый повторный запрос)isFetchingбудет иметь значениеtrue
Для большинства запросов имеет смысл сначала проверять состояние isLoading, затем состояние isError и, наконец, использовать данные для рендеринга:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Вместо логических значений можно использовать состояние status:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Ключи запроса¶
React Query осуществляет кэширование запросов на основе ключей. Ключи могут быть любыми уникальными сериализуемыми значениями (строками, массивами, объектами и т.д.).
Строковые ключи¶
При передачи строки в качестве ключа запроса, она преобразуется в массив с единственным элементом. Данный формат может использоваться для:
- Общих ресурсов списка/индекса
- Неиерархических ресурсов
1 2 | |
Ключи в виде массива¶
Когда для описания данных требуется больше информации, в качестве ключа запроса можно передать массив со строкой и любым количеством сериализуемых объектов. Такой формат может использоваться для:
- Иерархических или вложенных ресурсов
- В этом случае, обычно, передается идентификатор, индекс или другой примитив для определения элемента
- Запросов с дополнительными параметрами
- В этом случае, как правило, передается объект с дополнительными настройками
1 2 3 4 5 6 7 8 9 10 11 | |
Ключи хешируются детерменировано¶
Это означает, что порядок расположения ключей в объекте не имеет значения:
1 2 3 4 | |
А порядок расположения ключей в массиве, напротив, имеет значение:
1 2 3 4 | |
Обратите внимание, что если функция запроса использует переменную, такая переменная должна включаться в ключ запроса:
1 2 3 4 5 | |
Функции запроса¶
Функция запроса может быть любой функцией, возвращающей промис, который, в свою очередь, должен либо разрешаться данными, либо выбрасывать исключение:
1 2 3 4 5 6 | |
Обработка ошибок¶
При возникновении ошибки, функция запроса должна выбрасывать исключение, которое сохраняется в состоянии error запроса:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Переменные функции запроса¶
При необходимости, в функции запроса можно получить доступ к переменным, указанным в ключе запроса:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Использование объекта вместо параметров¶
Для настройки запроса можно использовать объект:
1 2 3 4 5 6 7 | |
Параллельные запросы¶
"Параллельными" называются запросы, которые выполняются одновременно.
Когда количество запросов остается неизменным, для их параллельного выполнения достаточно указать несколько хуков useQuery или useInfiniteQuery:
1 2 3 4 5 6 7 | |
Когда количество запросов меняется от рендеринга к рендерингу, для их параллельного выполнения следует использовать хук useQueries. Он принимает массив объектов с настройками запроса и возвращает массив результатов запросов:
1 2 3 4 5 6 7 8 | |
Зависимые запросы¶
Начало выполнения зависимых (или последовательных) запросов зависит от окончания выполнения предыдущих запросов. Для реализации последовательного выполнения запросов используется настройка enabled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Индикаторы получения данных в фоновом режиме¶
В большинстве случаев для отображения индикатора загрузки во время получения данных достаточно состояния status === 'loading'. Но иногда может потребоваться отображать индикатор для запроса, выполняющегося в фоновом режиме. Для этого запросы предоставляют дополнительное логическое значение isFetching:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Для отображения глобального индикатора загрузки при выполнении любого запроса (в том числе, в фоновом режиме) можно использовать хук useIsFetching:
1 2 3 4 5 6 7 8 9 | |
Выполнение повторного запроса при фокусировке на окне¶
Если пользователь покидает приложение и возвращается к устаревшим данным, React Query автоматически запрашивает свежие данные в фоновом режиме. Это поведение можно отключить глобально или в отношении конкретного запроса с помощью настройки refetchOnWindowFocus.
Глобальное отключение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Отключение для запроса:
1 2 3 | |
Отключение/приостановка выполнения запросов¶
Для отключения автоматического выполнения запроса используется настройка enabled.
При установка значения данной настройки в false:
- Если запрос имеет кэшированные данные
- Запрос будет инициализирован в состоянии
status === 'success'илиisSuccess - Если запрос не имеет таких данных
- Запрос бует запущен в состоянии
status === 'idle'илиisIdle - Запрос не будет автоматически выполняться при монтировании
- Запрос не будет автоматически выполняться повторно при монтировании или появлении нового экземпляра
- Запрос будет игнорировать вызовы
invalidateQueriesиrefetchQueriesна клиенте запроса (query client), обычно, приводящие к выполнению повторного запроса - Для ручного выполнения запроса может быть использован метод
refetch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | |
Повторное выполнение провалившихся запросов¶
При провале запроса, автоматически выполняется 3 попытки его повторного выполнения.
Это поведение можно изменить как на глобальном уровне, так и на уровне конкретного запроса:
- Установка
retry: falseотключает повторы - Установка
retry: 6увеличивает количество повторов до 6 - Установка
retry: trueделает повторы бесконечными - Устанока
retry: (failureCount, error) => {}позволяет кастомизировать обработку провала
1 2 3 4 5 6 | |
По умолчанию задержки между повторами составляют 2000, 4000 и 6000 мс. Это поведение можно изменить с помощью настройки retryDelay.
Запросы для пагинации¶
Для реализации пагинации с помощью React Query достаточно включить информацию о странице в ключ запроса:
1 | |
Тем не менее, если запустить данный пример, то обнаружится, что UI перепрыгивает между состояниями success и loading, поскольку каждая новая страница расценивается как новый запрос.
Для решения этой проблемы React Query предоставляет keepPreviousData. Установка данной настройки в значение true приводит к следующему:
- Данные последнего успешного запроса остаются доступными во время запроса новых данных
- После получения новых данных старые
dataмягко ими заменяются isPreviousDataпозволяет определять, какие данные предоставляются текущим запросом
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
keepPreviousData также работает с хуком useInfiniteQuery, что позволяет пользователям продолжать просмотр кэшированных данных при изменении ключей запроса.
Бесконечные запросы¶
Для запроса бесконечных списков (например, "загрузить еще" или "бесконечная прокрутка") React Query предоставляет специальную версию useQuery - useInfiniteQuery.
Особенности использования useInfiniteQuery:
data- объект, содержащий бесконечные данные:data.pages- массив полученных страницdata.pageParams- массив параметров страницы, использованных для запроса страниц- Доступны функции
fetchNextPageиfetchPreviousPage - Доступны настройки
getNextPageParamиgetPreviousPageParam, позволяющие определить наличие данных для загрузки - Доступно логическое значение
hasNextPage. Если оно равняетсяtrue,getNextPageParamвернет значение, а неundefined - Доступно логическое значение
hasPreviousPage. Если оно равняетсяtrue,getPreviousPageParamвернет значение, а неundefined - Доступны логические значения
isFetchingNextPageиisFetchingPreviousPage, позволяющие различать состояние фонового обновления и состояние дополнительной загрузки
Примеры¶
Предположим, что у нас имеется API, возвращающий страницы с тремя projects за раз на основе индекса cursor, а также сам курсор, который может быть использован для получения следующей группы проектов:
1 2 3 4 5 6 7 8 | |
Располагая этими сведениями, мы можем реализовать UI "Загрузить еще" посредством:
- Ожидания, пока
useInfiniteQueryвыполнить запрос на получение первой порции данных по умолчанию - Возвращения информации для следующего запроса в
getNextPageParam - Вызова функции
fetchNextPage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
По умолчанию переменная, возвращаемая из getNextPageParam, передается в функцию запроса. Пользовательская переменная, переданная в fetchNextPage, перезаписывает дефолтную:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Двунаправленный бесконечный список можно реализовать с помощью getPreviousPageParam, fetchPreviousPage, hasPreviousPage и isFetchingPreviousPage:
1 2 3 4 5 6 | |
Ручное удаление первой страницы:
1 2 3 4 | |
Ручное удаление значения из конкретной страницы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Заменители данных запроса¶
Данные-заменители (placeholder data) позволяют запросам рендерить частичный ("фейковый") контент до получения настоящих данных в фоновом режиме. Это дает результат, похожий на использование настройки initialData, но при этом данные не сохраняются в кэше.
Существует два способа помещения данных в кэш для использования в качестве заменителей:
- Декларативный:
- Предоставление запросу
placeholderDataдля заполнения пустого кэша - Императивный:
- Предварительное или обычное получение данных с помощью
queryClientи настройкиplaceholderData
Данные-заменители как значение¶
1 2 3 4 5 | |
Данные-заменители как функция¶
Если процесс получения данных-заменителей является интенсивным или мы не хотим, чтобы он повторялся при каждом рендеринге, тогда в качестве значения placeholderData можно мемоизировать значение или передать мемоизированную функцию:
1 2 3 4 5 6 7 8 9 | |
Данные-заменители из кэша¶
В некоторых случаях может потребоваться использовать в качестве данных-заменителей кэшированные результаты другого запроса. Хорошим примером такого случая является поиск кэшированных данных списка запросов поста в блоге для превью поста и последующее использование таких данных в качестве заменителя для запроса на получение конкретного поста:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Начальные данные запроса¶
Существует несколько способов помещения в кэш начальных данных для запроса:
- Декларативный
- Предоставление запросу
initialDataдля заполнения пустого кэша - Императивный
- Предварительное получение данных с помощью
queryClient.prefetchQuery - Ручное помещение данных в кэш с помощью
queryClient.setQueryData
Использование initialData для подготовки запроса¶
Если в нашем приложении имеются начальные данные для запроса, мы можем передать их в запрос с помощью настройки initialData, минуя состояние начальной загрузки.
1 2 3 4 5 | |
staleTime и initialDataUpdatedAt¶
По умолчанию initialData считаются свежими, как будто они были только что получены. Интерпретация начальных данных зависит от настройки staleTime (время устаревания):
- Если наблюдатель запроса был настроен с помощью
initialDataи не было указаноstaleTime(по умолчаниюstaleTimeравняется0), запрос будет выполнен повторно незамедлительно при монтировании:
1 2 3 4 5 6 | |
- Если наблюдатель запроса был настроен с помощью
initialDataи было указаноstaleTimeв количестве1000мс, данные будут считаться свежими на протяжении указанного времени:
1 2 3 4 5 6 7 | |
- Что если наши начальные данные не совсем свежие? Настройка
initialDataUpdatedAtпозволяет указывать время последнего обновления начальных данных в мс:
1 2 3 4 5 6 7 8 9 | |
Функция начальных данных¶
Если процесс получения начальных данных для запроса является интенсивным или мы не хотим, чтобы он повторялся при каждом рендеринге, то в качестве значения initialData можно передать функцию. Эта функция будет выполнена только один раз при инициализации запроса:
1 2 3 4 5 6 7 | |
Начальные данные из кэша¶
В некоторых случаях в качестве начальных данных запроса можно использовать кэшированные результаты другого запросаю. Хорошим примером такого случая является поиск кэшированных данных запроса списка задач для конкретной задачи и использование этих данных в качестве начальных для запроса на получение конкретной задачи:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Начальные данные из кэша с initialDataUpdatedAt¶
Для определения того, насколько начальные данные из кэша являются свежими и требуется ли выполнение повторного запроса используется настройка initialDataUpdatedAt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Условное получение начальных данных из кэша¶
Если мы не хотим использовать старый кэш в качестве начальных данных, то можем использовать метод queryClient.getQueryState для получения информации, включая state.dataUpdatedAt, которую можно использовать для определения того, достаточно ли свежими являются кэшированные данные:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Предварительное получение данных¶
Если у нас есть возможность предугадывать желания пользователей на получение определенных данных, мы можем выполнить предварительный запрос на их получение и заблаговременно поместить их в кэш:
1 2 3 4 | |
- Если данные для этого запроса уже имеются в кэше и не аннулированы, запрос выполнен не будет
- Если указано
staleTime, например,prefetchQuery('todos', fn, { staleTime: 5000 })и данные старше, чем значениеstaleTime, запрос будет выполнен - Если не имеется ни одного экземпляра
useQueryдля предварительного запроса, он будет удален и уничтожен сборщиком мусора по истечении времени, указанного вcacheTime
В качестве альтернативы, когда у нас уже имеются данные для запроса, нам не нужно выполнять предварительный запрос на их получение. Мы можем просто использовать метод setQueryData клиента запроса для добавления или обновления кэшированного результата запроса по ключу:
1 | |
Мутации¶
В отличие от запросов, мутации, обычно, используются для создания/обновления/удаления данных или для выполнения побочных эффектов на сервере. Для этого используется хук useMutation.
Пример мутации, добавляющей новую задачу на сервер:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
Мутация может находиться в одном из следующих состояний:
isIdleилиstatus === 'idle'- мутация находится в режиме ожидания или на стадии обновления/сбросаisLoadingилиstatus === 'loading'- мутация выполняетсяisErrorилиstatus === 'error'- при выполнении мутации возникла ошибкаisSuccessилиstatus === 'success'- мутация выполнена успешно, данные доступны
Также, в зависимости от состояния мутации, доступна дополнительная информация:
error- если мутация находится в состоянииisError, ошибка доступна через свойствоerrordata- если мутация находится в состоянииsuccess, данные доступны через свойствоdata
Функция mutate принимает переменную или объект.
При совместном использовании с настройкой onSuccess, методами invalidateQueries и setQueryData мутации становятся очень мощным инструментом.
Обратите внимание: функция mutate является асинхронной. Это означает, что мы не можем использовать ее в качестве колбека в обработчике событий. Для того, чтобы получить доступ к событию в onSubmit следует обернуть mutate в другую функцию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Сброс состояния мутации¶
В некоторых случаях требуется очистить error или data мутации. Для этого используется функция reset:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Побочные эффекты мутации¶
useMutation содержит некоторые утилиты, позволяющие выполнять побочные эффекты на любой стадии жизненного цикла мутации. Это может быть полезным как для инвалидации и повторного выполнения запроса после мутации, так и для оптимистического обновления:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
При возвращении промиса из любого колбека, следующий промис будет ждать разрешения предыдущего:
1 2 3 4 5 6 7 8 | |
При вызове mutate можно определять дополнительные колбеки, кроме тех, что определены в useMutation. Это может использоваться для запуска побочных эффектов, специфичных для компонента. Поддерживается перезапись таких методов, как onSuccess, onError и onSettled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Промисы¶
Для получения промиса, который разрешается в случае успеха или выбрасывает ошибку, следует использовать mutateAsync вместо mutate. Это может использоваться для создания композиции из побочных эффектов:
1 2 3 4 5 6 7 8 9 10 | |
Повторное выполнение мутации¶
По умолчанию React Query не запускает повторное выполнение мутации при возникновении ошибки, но это можно изменить с помощью настройки retry:
1 2 3 | |
Сохранение мутации¶
Мутации могут быть сохранены в хранилище с помощью дегидрации/гидрации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
Инвалидация запросов¶
Метод invalidateQueries клиента запроса позволяет помечать запроса как устаревшие и потенциально выполнять повторное получение данных:
1 2 3 4 | |
При инвалидации запроса с помощью invalidateQueries происходит две вещи:
- Он помечается как устаревший. При этом, настройка
staleTimeвuseQueryи других хуках перезаписывается - Если запрос отрендерен с помощью
useQueryили других хуков, он выполняется повторно в фоновом режиме
Поиск совпадений с помощью invalidateQueries¶
При использовании таких API, как invalidateQueries и removeQueries (и других, поддерживающих поиск частичного совпадения), можно осуществлять поиск совпадения по префиксу или искать полного совпадения.
В следующем примере мы используем префикс todos для инвалидации любых запросов, ключи которых начинаются с todos:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
В метод invalidateQueries можно передавать переменные для инвалидации конкретных запросов:
1 2 3 4 5 6 7 8 9 10 | |
API invalidateQueries является очень гибким, поэтому, если мы хотим аннулировать запросы с ключом todos без дополнительных переменных или ключей, то можем передать настройку exact: true:
1 2 3 4 5 6 7 8 9 10 | |
В invalidateQueries также можно передавать функцию-предикат. Данная функция будет принимать каждый экземпляр Query и возвращать true или false в зависимости от условия:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Инвалидация запросов с помощью мутаций¶
Инвалидация запросов - это половина успеха. Вторая половина - знать, когда их следует аннулировать. Обычно, при выполнении мутации, связанные с ней запросы нуждаются в инвалидации и повторном выполнении.
Предположим, что у нас имеется мутация для добавления новой задачи:
1 | |
После выполнении мутации postTodo, нам требуется аннулировать все запросы с ключом todos и выполнить их повторно для отображения новой задачи. Для этого мы можем использовать настройку onSuccess и функцию invalidateQueries:
1 2 3 4 5 6 7 8 9 10 11 | |
Инвалидация может выполняться в любом колбеке хука useMutation.
Выполнение обновлений с помощью данных, возвращаемых мутацией¶
При выполнении мутаций, обновляющих объект на сервере, в ответ, как правило, возвращается обновленный объект. Вместо повторного выполнения запросов, возвращенный объект можно использовать для обновления существующего запроса с помощью метода setQueryData клиента запроса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Логику onSuccess можно вынести в переиспользуемую мутацию (пользовательский хук):
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Оптимистические обновления¶
Обработчик onMutate позволяет вернуть значение, которое затем передается в обработчики onError и onSettled в качестве последнего аргумента. Это может быть использовано для передачи функции отмены.
Обновление списка задач при добавлении новой задачи¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
Обновление одной задачи¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
Вместо обработчиков onError и onSuccess можно использовать функцию onSettled:
1 2 3 4 5 6 7 8 | |
Отмена запроса¶
React Query предоставляет общий способ отмены запросов с помощью токена отмены или другого API. Для этого необходимо добавить функцию cancel к промису, возвращаемому запросом, в котором реализуется логика отмены. Когда запрос становится неактивным или устаревшим, автоматически вызывается функция promise.cancel.
С помощью axios¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
С помощью fetch¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Ручная отмена¶
В случае, когда, например, выполнение запроса занимает слишком много времени, мы можем предоставить пользователю возможность отмены такого запроса. Это можно реализовать с помощью вызова queryClient.cancelQueries(key). Если promise.cancel является доступным, React Query отменит запрос:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
Фильтры запроса¶
Некоторые методы React Query принимают объект QueryFilters. Фильтры запроса - это объект с условиями для поиска совпадения с запросом:
1 2 3 4 5 6 7 8 9 10 11 | |
Объект QueryFilters имеет следующие свойства:
exact- поиск точного совпадения с ключом запросаactive- поиск совпадения с активными/неактивными запросамиinactive- поиск совпадения с неактивными/активными запросамиstale- поиск совпадения с устаревшими/свежими запросамиfetching- поиск совпадения с запросами, находящимися на стадии выполнения, или со всеми остальными запросамиpredicate- функция-предикат, вызываемая для каждого запроса и возвращающаяtrueдля найденных запросовqueryKey- ключ для поиска совпадения
Рендеринг на стороне сервера¶
React Query поддерживает два способа предварительного получения данных на сервере и передачи их клиенту запроса:
- Самостоятельное предварительное получение данных и их передача в качестве
initialData - Подходит для быстрой настройки в простых случаях
- Имеет некоторые ограничения
- Предварительное выполнение запроса на сервере, дегидрация кэша и регидрация на клиенте
- Требует дополнительной настройки
Использование Next.js¶
Рекомендуется использовать Next.js, который поддерживает две формы предварительного рендеринга:
- Генерация статического контента, статических сайтов (SSG)
- Рендеринг на стороне сервера (SSR)
Использование initialData¶
С помощью getStaticProps или getServerSideProps из Next.js можно передавать запрашиваемые данные в настройку initialData хука useQuery:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Это минимальная настройка, подходящая для простых случаев, но она имеет некоторые ограничения:
- Вызов
useQueryв глубоко вложенном компоненте предполагает передачуinitialDataв этот компонент - Вызов
useQueryв нескольких местах предполагает передачуinitialDataво все эти места - Не существует способа определить, когда данные были получены на сервере, поэтому
dataUpdatedAtи определение того, нуждается ли запрос в повторном выполнении, зависит от времени загрузки страницы
Использование гидрации¶
React Query поддерживает предварительное выполнение нескольких запросов на сервере в Next.js и дегидрацию этих запросов на клиенте. Это означает, что сервер может предварительно отрендерить необходимую разметку, которая доступна при загрузке страницы и, как только станет доступным JS, React Query обновит эти запросы, используя весь функционал библиотеки. Это включает в себя повторное выполнение устаревших запросов.
Для кэширования запросов на сервере и настройки гидрации необходимо сделать следующее:
- Создать новый экземпляр
QueryClientвнутри приложения и в экземпляреref. Это позволяет предотвратить распределение данных между разными пользователями и запросами - Обернуть компонент приложения в
QueryClientProviderи передать ему экземпляр клиента - Обернуть компонент приложения в
Hydrateи передать ему пропdehydrateStateизpageProps
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
После этого необходимо выполнить предварительные запросы на получение данных с помощью getStaticProps (для SSG) или getServerSideProps (для SSR):
- Создаем новый экземпляр
QueryClientдля каждой запрашиваемой страницы. Это позволяет предотвратить распределение данных между разными пользователями и запросами - Выполняем предварительный запрос с помощью клиентского метода
prefetchQueryи ждем его завершения - Используем
dehydrateдля дегидрации кэша запроса и передаем его странице через пропdehydrateState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Дефолтная функция запроса¶
При создании клиента запроса, ему можно передать функцию для выполнения запроса, которая будет использоваться по умолчанию. Для этого используется настройка defaultOptions.queries.queryFn экземпляра queryClient:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
Для перезаписи дефолтной функции достаточно передать в запрос другую функцию.
Suspense¶
React Query может использоваться совместно с React Suspense. Для включения соответствующего режима следует установить значение настройки suspense в true.
Глобальная настройка:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Настройка запроса:
1 2 3 | |
При включении данного режима состояния status и объект error становятся недоступными и заменяются соответствующими элементами React.Suspense (включая проп fallback и предохранители для перехвата ошибок).
Мутации также ведут себя несколько иначе. По умолчанию, вместо генерации переменной error, мутация выбрасывает исключение при следующем рендеринге использующего ее компонента для передачи ближайшему предохранителю. Для отключения такого поведения следует установить настройку useErrorBoundary в значение false. Для отключения всех ошибок следует установить настройку throwOnError также в значение false.
Сброс ошибок¶
Ошибки запросов могут быть сброшены с помощью компонента QueryErrorResetBoundary или хука useQueryErrorResetBoundary.
При использовании компонента будут сброшены все ошибки, перехваченные предохранителем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
При использовании хука будут сброшены ошибки из ближайшего QueryErrorResetBoundary. Если предохранитель не определен, ошибки будут сброшены глобально:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Плагины¶
Обратите внимание: данная технология является экспериментальной и может измениться в будущем.
persistQueryClient¶
persistQueryClient - это утилита, позволяющая сохранять клиента запроса и его кэш для последующего использования. На сегодняшний день для этого доступен только один плагин - createLocalStoragePersistor.
Импорт:
1 | |
Пример использования.
Импортируем функцию persistQueryClient и передаем ей экземпляр QueryClient (с настройкой cacheTime) и интерфейс Persistor :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
API¶
persistQueryClient- принимает экземплярQueryClientиpersistor
1 | |
Настройки:
queryClient- клиент запросаpersistor- интерфейс для сохранения/восстановления кэшаmaxAge:- максимальный возраст кэшаbuster- уникальная строка, которая может использоваться для принудительной инвалидации кэша
Дефолтные настройки:
1 2 3 4 | |
createLocalStoragePersistor¶
Импорт:
1 | |
Пример использования.
- Импортируем функцию
createLocalStoragePersistor - Создаем новый
localStoragePersistor - Передаем его в функцию
persistQueryClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
API¶
createLocalStoragePersistor- принимает опциональный объект с настройками и возвращаетlocalStoragePersistor, передаваемый вpersisteQueryClient
1 | |
Настройки:
localStorageKey- ключ, используемый для сохранения кэшаthrottleTime- "троттлинг" записи значений в хранилище
Дефолтные настройки:
1 2 3 4 | |
broadcastQueryClient¶
broadcastQueryClient - это утилита для распространения (распределения, вещания) и синхронизации состояния клиента запроса между вкладками/окнами браузера одного источника (same origin).
Импорт:
1 | |
Пример использования.
Импортируем функцию broadcastQueryClient и передаем ей экземпляр QueryClient и, опционально, устанавливаем broadcastChannel:
1 2 3 4 5 6 7 8 | |
API¶
broadcastQueryClient- принимает экземплярQueryClientи, опционально,broadcastChannel
1 | |
Настройки:
queryClient- клиент запросаbroadcastChannel- уникальное название канала, которое будет использоваться для коммуникации между вкладками/окнами
Дефолтные настройки:
1 2 3 | |
Основные части API¶
useQuery¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
Основные настройки¶
queryKey | array- уникальная строка или массив: ключ для идентификации запроса. Запрос автоматически обновляется при изменении данного ключа (еслиenabledне установлено вfalse)queryFn- функция для получения данных. Принимает объектQueryFunctionContextс переменнойqueryKey. Должна возвращать промис, разрешающийся данными или выбрасывающий исключениеenabled- автоматическое выполнение запросаretry- количесто попыток выполнения запроса (true- бесконечное количество попыток)retryDelay- функция, принимающее целое числоretryAttemptи ошибку и возвращающая задержку, применяемую перед следующей попыткой выполнения запроса в мс. Функцияattempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)применяет экспоненциальную задержку. Функцияattempt => attempt * 1000применяет линейную задержкуstaleTime- время в мс, по истечении которого данные считаются устаревшими (Infinity- данные всегда будут считаться свежими)cacheTime- время в мс, в течение которого неиспользуемый/неактивный кэш сохраняется в памяти, после чего такой кэш уничтожается сборщиком мусора (Infinity- кэш никогда не будет собран)refetchInterval- периодическое повторное выполнение запроса (период определяется в мс)notifyOnChangeProps- компонент будет перерисовываться только при изменении указанных свойств. Например, при указании['data', 'error'], компонент будет перерисовываться только при измененииdataилиerror.trackedозначает отслеживание доступа к свойствам: компонент будет перерисовываться только при изменении одного из отслеживаемых свойствonSuccess- функция, вызываемая при успехе запроса. Получает объектdataonError- функция, вызываемая при провале запроса. Получает объектerroronSettled- функция, вызываемая как при успехе, так и при провале запроса. Получает объектыdataиerrorselect- функция, используемая для преобразования или выборки данныхinitialData- начальное значение для кэша запроса. Если значением является функция, она будет вызвана только один раз при инициализации запроса и должна синхронно возвращать начальные данные. Начальные данные считаются устаревшими, если не установленоstaleTimeinitialDataUpdatedAt- время в мс последнего обновленияinitialDataplaceholderData- данные-заменители, используемые до получения настоящих данных и при отсутствииinitialDatakeepPreviousData- сохранение предыдущих данных при запросе новых (при изменении ключа запроса)
Основные возвращаемые значения¶
status- возможные значения:idle- возможно только при инициализации запроса сenabled: falseи отсутствии начальных данныхloading- запрос находится на стадии выполненияerror- выполнение запроса завершилось ошибкойsuccess- выполнение запроса завершилось успешно. Соответствующее свойствоdata- это данные, полученные из успешного запроса, или, приenabled: false, начальные данные- производные логические значения из переменной
status: isIdleisLoadingisErrorisSuccessdata- последние успешно разрешенные данные для запроса (по умолчаниюundefined)error- объект ошибки для запроса (по умолчаниюnull)isFetching-true, если запрос находится в процессе выполнения (включая выполнение в фоновом режиме)refetch- функция для ручного выполнения повторного запросаremove- функция для удаления запроса из кэша
useQueries¶
Хук useQueries может использоваться для выполнения нескольких запросов:
1 2 3 4 | |
Хук принимает массив объектов с настройками запроса и возвращает массив результатов выполнения этих запросов.
useInfiniteQuery¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Настройки¶
Настройки для useInfiniteQuery идентичны настройкам useQuery, кроме следующих:
queryFn- функция для получения данных. Принимает объектQueryFunctionContextс переменнымиqueryKeyиpageParam. Должна возвращать промис, разрешающийся данными или выбрасывающий исключение. Убедитесь, что возвращаете данные иpageParam, если собираетесь использовать последнийgetNextPageParam- при получении новых данных запросом, эта функция получает последнюю страницу бесконечного списка данных и массив всех страниц. Функция должна возвращать переменную, которая передается в качестве последнего опционального аргумента в функцию запроса. Возвращаетundefinedпри отсутствии следующей страницыgetPreviousPageParam- аналогична функцииgetNextPageParam, но в отношении предыдущей страницы
Основные возвращаемые значения¶
Значения, возвращаемые useInfiniteQuery, идентичны значениям, возвращаемым useQuery, кроме следующих:
data.pages- массив, содержащий все страницыdata.pageParams- массив, содержащий все параметры страницыfetchNextPage- функция, позволяющая получать следующую "страницу" результатов.options.pageParamпозволяет вручную определять параметр страницы вместо использованияgetNextPageParamfetchPreviousPage- аналогична функцииfetchNextPage, но в отношении предыдущей страницыhasNextPage- имеется ли следующая страница для полученияhasPreviousPage- имеется ли предыдущая страница для получения
useMutation¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
Настройки¶
mutationFn- функция, выполняющая асинхронную задачу и возвращающая промис. Принимает объектvariablesдля мутированияmutationKey- строка, которая может использовать для наследования дефолтных настроек с помощьюqueryClient.setMutationDefaults, а также для идентификации мутации в инструментах разработчикаonMutate- функция, выполняемая перед мутацией, принимающая те же переменные. Может использоваться для выполнения оптимистических обновлений в надежде, что мутация завершится успешно. В случае провала мутации, возвращаемое этой функцией значение будет передано вonErrorиonSettledonSuccess- функция, выполняемая при успехе мутации. Если возвращается промис, он будет разрешен перед продолжениемonError- функция, выполняемая при провале мутации. Если возвращается промис, он будет разрешен перед продолжениемonSettled- функция, выполняемая как при успехе, так и при провале мутации. Если возвращается промис, он будет разрешен перед продолжениемretry- количество попыток повторного выполнения мутации (true- бесконечное количество попыток)retryDelay- аналогичнаretryDelayзапросаuseErrorBoundary- еслиtrue, ошибки, возникшие при выполнении мутации, выбрасываются на стадии рендеринга и передаются ближайшему предохранителю
Возвращаемые значения¶
mutate- функция мутации, которая может вызываться с переменными для запуска мутации и, опционально, для перезаписи настроек, переданных вuseMutationmutateAsync- функция, аналогичнаяmutate, но возвращающая промисstatus- возможные значения:idleloadingerrorsuccessisIdle,isLoading,isError,isSuccess- производные логические значенияstatusdata- последние успешно разрешенные данные для запроса (по умолчаниюundefined)error- объект ошибки для запроса (по умолчаниюnull)reset- функция для очистки внутреннего состояния мутации
useIsFetching¶
useIsFetching - опциональный хук, возвращающий количество запросов, выполняемых в фоновом режиме. Может использоваться для глобальных индикаторов загрузки:
1 2 3 4 5 | |
Настройки:
queryKey- ключ запросаfilters- фильтры запроса
Возвращается количество запросов, выполняемых в фоновом режиме.
QueryClient¶
QueryClient может использоваться для взаимодействия с кэшем:
1 2 3 4 5 6 7 8 9 10 11 | |
Доступные методы:
fetchQueryfetchInfiniteQueryprefetchQueryprefetchInfiniteQuerygetQueryDatasetQueryDatagetQueryStateinvalidateQueriesrefetchQueriescancelQueriesremoveQueriesresetQueriesisFetchinggetDefaultOptionssetDefaultOptionsgetQueryDefaultssetQueryDefaultsgetMutationDefaultssetMutationDefaultsgetQueryCachegetMutationCacheclear
Настройки¶
queryCache- кэш запроса, к которому подключен данный клиентmutationCache- кэш мутации, к которому подключен данный клиентdefaultOptions- настройки по умочанию для всех запросов и мутаций
Методы¶
fetchQuery¶
fetchQuery - это асинхронный метод, который может использоваться для выполнения и кэширования запроса. Если нам не нужен результат запроса, то вместо fetchQuery можно использовать prefetchQuery.
Если запрос существует и данные не аннулированы или не старше указанного staleTime, то возвращаются данные из кэша. В противном случае, предпринимается попытка получения последних данных.
Разница между fetchQuery и setQueryData состоит в том, что fetchQuery является асинхронным и позволяет убедиться, что для данного запроса с помощью экземпляров useQuery не было создано дублирующихся запросов (для отрендеренного запроса во время получения данных).
1 2 3 4 5 6 7 8 | |
С помощью staleTime можно обеспечить выполнение запроса, когда данные становятся устаревшими:
1 2 3 4 5 6 7 8 9 10 11 | |
fetchInfiniteQuery¶
fetchInfiniteQuery аналогичен fetchQuery, но используется для выполнения и кэширования бесконечных запросов:
1 2 3 4 5 6 7 8 9 | |
prefetchQuery¶
prefetchQuery - это асинхронный метод, который может использоваться для предварительного выполнения запроса, перед его выполнением или рендерингом с помощью useQuery. В целом, данный метод работает так же, как fetchQuery.
prefetchInfiniteQuery¶
prefetchInfiniteQuery аналогичен prefetchQuery, но используется для предварительного выполнения и кэширования бесконечных запросов.
getQueryData¶
getQueryData - синхронная функция для получения существующих кэшированных данных запроса. Если запроса не существует, возвращается undefined.
1 | |
setQueryData¶
setQueryData - синхронная функция для незамедлительного обновления кэшированных данных запроса. Если запроса не существует, он создается. Если запрос не используется хуком в течение дефолтных пяти минут cacheTime, он уничтожается сборщиком мусора.
1 | |
getQueryState¶
getQueryState - синхронная функция для получения существующего состояния запроса. Если запроса не существует, возвращается undefined
1 2 | |
invalidateQueries¶
Метод invalidateQueries используется для инвалидации и обновления одного или нескольких запросов в кэше на основе их ключей или других доступных свойств/состояния. По умолчанию, все совпавшие запросы помечаются как аннулированные и активные запросы повторно выполняются в фоновом режиме.
- Для отключения повторного выполнения активных запросов используется настройка
refetchActive: false - Для повторного выполнения неактивных запросов используется настройка
refetchInactive: true
1 2 3 4 5 6 7 8 9 | |
refetchQueries¶
Метод refetchQueries используется для повторного выполнения запросов на основе определенных условий.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
cancelQueries¶
Метод cancelQueries используется для отмены исходящих запросов на основе их ключей или любых других доступных свойств/состояния.
Это может быть полезным при выполнении оптимистических обновлений, когда мы не хотим, чтобы выполняющиеся запросы перезаписывали такие обновления.
1 | |
removeQueries¶
Метод removeQueries используется для удаления запросов из кэша на основе их ключей или любых других доступных свойств/состояния.
1 | |
resetQueries¶
Метод resetQueries используется для сброса запросов в кэше к их начальному состоянию на основе ключей или любых других доступных свойств/состояния.
Данный метод уведомляет подписчиков, в отличие от clear, который удаляет подписчиков. Если запрос имеет initialData, он будет сброшен к ним. Если запрос является активным, он будет выполнен повторно.
1 | |
isFetching¶
Метод isFetching похож на хук useIsFetching. Он возвращает количество запросов, находящихся в процессе выполнения.
1 2 3 4 5 | |
getDefaultOptions и setDefaultOptions¶
getDefaultOptions возвращает настройки по умолчанию, определенные при создании клиента или с помощью setDefaultOptions. setDefaultOptions позволяет динамически устанавливать дефолтные настройки для данного клиента.
getQueryDefaults и setQueryDefaults¶
Аналогичны getDefaultOptions и setDefaultOptions, но в отношении конкретных запросов.
getMutationDefaults и setMutationDefaults¶
Аналогичны getDefaultOptions и setDefaultOptions, но в отношении конкретных мутаций.
getQueryCache и getMutationCache¶
Данные методы возвращают, соответственно, кэш запроса и кэш мутации, к которым подключен данный клиент.
clear¶
Метод clear очищает все подключенные кэши.
1 | |
QueryClientProvider¶
QueryClientProvider используется для подключения и передачи QueryClient в приложение:
1 2 3 4 5 6 7 8 9 10 11 12 | |
useQueryClient¶
Хук useQueryClient возвращает текущий экземпляр QueryClient:
1 2 3 | |
QueryObserver¶
QueryObserver используется для наблюдения и переключения между запросами:
1 2 3 4 5 6 7 8 | |
InfiniteQueryObserver¶
InfiniteQueryObserver используется для наблюдения и переключения между бесконечными запросами:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
QueriesObserver¶
QueriesObserver используется для наблюдения за несколькими запросами:
1 2 3 4 5 6 7 8 9 | |