React Query
React Query - это библиотека для получения, кеширования, синхронизации и обновления состояния
React-приложениях
, хранящегося на сервере.
Обзор
Установка
yarn add react-query
# или
npm i react-query
Пример
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
export const App = () => (
<QueryClientProvider>
<Example />
</QueryClientProvider>
)
function Example() {
const { isLoading, error, data } = useQuery('repoData', () => fetch('https://api.github.com/repos/tannerlinsley/react-query').then(response => response.json()))
if (isLoading) return <p>Загрузка...</p>
if (error) return <p>Ошибка: {error.message}</p>
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
Быстрый старт
Ключевыми концепциями React Query
являются:
- Запросы (queries)
- Мутации (mutations)
- Инвалидация (аннулирование, признание недействительным) запроса (query invalidation), его кэша
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider
} from 'react-query'
import { getTodos, postTodo } from '../api'
// Создаем клиента
const queryClient = new QueryClient()
const App = () => (
// Передаем клиента в приложение
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
function Todos() {
// Получаем доступ к клиенту
const queryClient = useQueryClient()
// Запрос
const query = useQuery('todos', getTodos)
// Мутация
const mutation = useMutation(postTodo, {
onSuccess: () => {
// Инвалидация и обновление
queryClient.invalidateQueries('todos')
}
})
return (
<div>
<ul>
{query.data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
<button
onClick={() => {
mutation.mutate({
id: Date.now(),
title: 'Привет',
})
}}
>
Добавить задачу
</button>
</div>
)
}
render(<App />, document.getElementById('root'))
Инструменты разработчика
По умолчанию инструменты разработчика не включаются в производственную сборку (когда process.env.NODE_ENV === 'production'
).
Существует два режима добавления инструментов в приложение: плавающий (floating) и встроенный (inline).
Плавающий режим
import { ReactQueryDevtools } from 'react-query/devtools'
const App = () => (
<QueryClientProvider client={queryClient}>
{/* Другие компоненты */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
Настройки
initialIsOpen
- еслиtrue
, то панель инструментов по умолчанию будет открытаpanelProps
- используется для добавления про пов к панели, например,className
,style
и т.д.closeButtonProps
- используется для добавления пропов к кнопке закрытия панелиtoggleButtonProps
- используется для добавления пропов к кнопке переключенияposition
: "top-left" | "top-right" | "bottom-left" | "bottom-right" - положение логотипаReact Query
для открытия/закрытия панели
Встроенный режим
import { ReactQueryDevtoolsPanel } from 'react-query/devtools'
const App = () => (
<QueryClientProvider client={queryClient}>
{/* Другие компоненты */}
<ReactQueryDevtoolsPanel style={styles} className={className} />
</QueryClientProvider>
)
Важные настройки по умолчанию
- Экземпляры запроса(query instances), созданные с помощью
useQuery
илиuseInfiniteQuery
по умолчанию считают кэшированные данные устаревшими (stale) - Устаревшие запросы автоматически повторно выполняются в фоновом режиме (background) в следующих случаях:
- Монтирование нового экземпляра запроса
- Переключения окна в браузере
- Повторное подключение к сети
- Когда задан интервал выполнения повторного запроса (refetch interval)
- Результаты запроса, не имеющие активных экземпляров
useQuery
,useInfiniteQuery
или наблюдателей (observers) помечаются как "неактивные" (inactive) и остаются в кэше - По умолчанию запросы, помеченные как неактивные, уничтожаются сборщиком мусора через 5 минут
- Провалившиеся запросы автоматически повторно выполняются 3 раза с экспоненциально увеличивающейся задержкой перед пер едачей ошибки в UI
- Результаты запросов по умолчанию структурно распределяются для определения того, действительно ли данные изменились, и если это не так, ссылка на данные остается неизменной, что способствует стабилизации данных при использовании
useMemo
иuseCallback
Запросы
Основы
Запрос - это декларативная зависимость асинхронного источника данных, связанного с уникальным ключом. Запрос может использоваться с любым основанным на промисе методом (включая методы GET
и POST
) получения данных от сервера. Если метод изменяет данные на сервере, то лучше использовать мутации.
Для подписки на запрос в компоненте или пользовательском хуке следует вызвать хук useQuery
со следующими параметрами:
- Уникальный ключ запроса
- Функция, возвращающая промис, который
- разрешается данными (resolve data) или
- выбрасывает исключение (throw error)
import { useQuery } from 'react-query'
function App() {
const info = useQuery('todos', fetchTodoList)
}
Уникальный ключ используется для выполнения повторных запросов, кэширования и распределения запросов в приложении.
Результаты запроса, возвращаемые useQuery
, содержат всю необходимую информацию о запросе.
const result = useQuery('todos', fetchTodos)
Объект result
содержит несколько важных состояний (states). Запрос может находиться в одном из следующих состояний:
isLoading
илиstatus === 'loading'
- запрос находится на стадии выполнения, данные еще не полученыisError
илиstatus === 'error'
- запрос завершился ошибкойisSuccess
илиstatus === 'success'
- запрос завершился успешно, данные доступныisIdle
илиstatus === 'idle'
- запрос отключен
Кроме того, объект result
содержит следующую информацию:
error
- если запрос находится в состоянииisError
, ошибка доступна через свойствоerror
data
- если запрос находится в состоянииsuccess
, данные доступны через свойствоdata
isFetching
- в любом состоянии, если запрос находится на стадии выполнения (включая фоновый повторный запрос)isFetching
будет иметь значениеtrue
Для большинства запросов имеет смысл сначала проверять состояние isLoading
, затем состояние isError
и, наконец, использовать данные для рендеринга:
function Todos() {
const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
if (isLoading) {
return <span>Загрузка...</span>
}
if (isError) {
return <span>Ошибка: {error.message}</span>
}
// На данном этапе мы можем предположить, что `isSuccess === true`
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Вместо логических значений можно использовать состояние status
:
function Todos() {
const { status, data, error } = useQuery('todos', fetchTodoList)
if (status === 'loading') {
return <span>Загрузка...</span>
}
if (status === 'error') {
return <span>Ошибка: {error.message}</span>
}
// status === 'success'
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}