Endpoint — это описание конкретной операции взаимодействия с сервером
внутри API-слайса, созданного через createApi. Каждый
endpoint отвечает за один тип действия:
В RTK Query endpoints объявляются внутри свойства
endpoints:
import { createApi, fetchBaseQuery } fr om '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api'
}),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users'
})
})
})
Функция endpoints принимает объект builder,
через который создаются endpoint-описания.
RTK Query поддерживает два основных типа endpoint:
querymutationКаждый тип имеет собственное назначение, особенности кэширования и поведение.
query используется для получения данных.
Основная задача query-endpoint — загрузка информации с сервера и хранение результата в кэше RTK Query.
Пример:
getPosts: builder.query({
query: () => '/posts'
})
Такой endpoint автоматически создаёт React hook:
const { data, isLoading } = useGetPostsQuery()
RTK Query сохраняет результаты запросов в store Redux.
Если несколько компонентов используют один и тот же query с одинаковыми аргументами, сетевой запрос будет выполнен только один раз.
getUser: builder.query({
query: (id) => `/users/${id}`
})
useGetUserQuery(5)
Повторный вызов:
useGetUserQuery(5)
не отправит новый запрос, если данные уже находятся в кэше.
RTK Query автоматически создаёт состояния:
const {
data,
error,
isLoading,
isFetching,
isSuccess,
isError
} = useGetUsersQuery()
Каждое поле отражает текущее состояние запроса.
Query-endpoints поддерживают автоматические повторные обновления данных:
useGetPostsQuery(undefined, {
pollingInterval: 5000
})
RTK Query будет обновлять данные каждые 5 секунд.
useGetPostsQuery(undefined, {
refetchOnFocus: true
})
При возврате во вкладку браузера данные обновятся автоматически.
useGetPostsQuery(undefined, {
refetchOnReconnect: true
})
После восстановления интернет-соединения RTK Query выполнит повторный запрос.
Полная структура:
builder.query({
query,
transformResponse,
transformErrorResponse,
providesTags,
keepUnusedDataFor,
serializeQueryArgs,
merge,
forceRefetch,
onQueryStarted,
onCacheEntryAdded
})
Каждое свойство отвечает за отдельный аспект поведения endpoint.
query описывает параметры HTTP-запроса.
getUsers: builder.query({
query: () => '/users'
})
getUser: builder.query({
query: (id) => `/users/${id}`
})
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
})
})
Можно указывать:
urlmethodbodyparamsheadersАргументы query участвуют в формировании ключа кэша.
getPosts: builder.query({
query: ({ page, lim it }) => ({
url: '/posts',
params: {
page,
limit
}
})
})
Использование:
useGetPostsQuery({
page: 1,
limit: 20
})
RTK Query сериализует аргументы и создаёт уникальный cache key.
Позволяет преобразовывать ответ сервера до сохранения в store.
getUsers: builder.query({
query: () => '/users',
transformResponse: (response) => {
return response.data
}
})
Полезно при работе с API следующего вида:
{
"success": true,
"data": []
}
Позволяет преобразовывать ошибки.
getUsers: builder.query({
query: () => '/users',
transformErrorResponse: (response) => {
return response.data.message
}
})
Используется для связывания query с системой инвалидации кэша.
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Posts']
})
После мутации RTK Query сможет автоматически обновить этот query.
Указывает время хранения кэша после удаления последнего подписчика.
getPosts: builder.query({
query: () => '/posts',
keepUnusedDataFor: 60
})
Данные будут храниться 60 секунд.
Позволяет кастомизировать формирование cache key.
getPosts: builder.query({
query: (params) => ({
url: '/posts',
params
}),
serializeQueryArgs: ({ endpointName }) => {
return endpointName
}
})
Все запросы начнут использовать единый кэш.
Используется для объединения старого и нового кэша.
Особенно полезно при пагинации.
getPosts: builder.query({
query: (page) => `/posts?page=${page}`,
serializeQueryArgs: ({ endpointName }) => endpointName,
merge: (currentCache, newItems) => {
currentCache.push(...newItems)
}
})
Определяет необходимость принудительного обновления.
forceRefetch({ currentArg, previousArg }) {
return currentArg !== previousArg
}
Позволяет реагировать на начало запроса.
Часто используется для optimistic update.
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData(
'getPosts',
undefined,
(draft) => {
const post = draft.find(p => p.id === arg.id)
if (post) {
Object.assign(post, arg)
}
}
)
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}
})
Позволяет реагировать на создание и удаление кэша.
Часто используется для WebSocket.
getNotifications: builder.query({
query: () => '/notifications',
async onCacheEntryAdded(
arg,
{
updateCachedData,
cacheDataLoaded,
cacheEntryRemoved
}
) {
await cacheDataLoaded
const socket = new WebSocket('ws://localhost:3000')
socket.onmess age = (event) => {
const data = JSON.parse(event.data)
updateCachedData((draft) => {
draft.push(data)
})
}
await cacheEntryRemoved
socket.close()
}
})
mutation используется для изменения данных на
сервере.
Примеры:
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
})
})
RTK Query создаст hook:
const [createPost, result] = useCreatePostMutation()
const [
createPost,
{
data,
error,
isLoading,
isSuccess,
isError
}
] = useCreatePostMutation()
await createPost({
title: 'New post'
})
Метод unwrap позволяет получить чистый результат или
выбросить ошибку.
try {
const result = await createPost(data).unwrap()
console.log(result)
} catch (error) {
console.error(error)
}
Без unwrap mutation всегда возвращает action object.
Mutation обычно инвалидирует query-кэш.
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: ['Posts']
})
После успешной мутации RTK Query автоматически перезапросит все query
с providesTags: ['Posts'].
providesTags: ['Posts']
Endpoint сообщает:
«Я предоставляю эти данные»
invalidatesTags: ['Posts']
Endpoint сообщает:
«Эти данные устарели»
Теги могут зависеть от результата.
getPosts: builder.query({
query: () => '/posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({
type: 'Posts',
id
})),
{ type: 'Posts', id: 'LIST' }
]
: [{ type: 'Posts', id: 'LIST' }]
})
updatePost: builder.mutation({
query: ({ id, ...body }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body
}),
invalidatesTags: (result, error, arg) => [
{
type: 'Posts',
id: arg.id
}
]
})
RTK Query не запрещает использовать POST внутри query.
searchPosts: builder.query({
query: (filters) => ({
url: '/posts/search',
method: 'POST',
body: filters
})
})
Главный критерий:
Крупные API обычно группируют по сущностям.
endpoints: (builder) => ({
getUsers: builder.query(...),
getUser: builder.query(...),
getPosts: builder.query(...),
createPost: builder.mutation(...),
getComments: builder.query(...)
})
RTK Query поддерживает динамическое расширение API.
const extendedApi = api.injectEndpoints({
endpoints: (builder) => ({
getProfile: builder.query({
query: () => '/profile'
})
})
})
Особенно важно для:
api.injectEndpoints({
overrideExisting: true,
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users'
})
})
})
Имена endpoint имеют большое значение.
Из имени автоматически формируются:
getUsers
Превращается в:
useGetUsersQuery
Mutation:
createPost
Превращается в:
useCreatePostMutation
Используются префиксы:
getfetchloadПримеры:
getUsers
getUser
fetchPosts
loadProfile
Используются глаголы действия:
createPost
updatePost
deletePost
login
logout
uploadAvatar
Каждый endpoint проходит через жизненный цикл:
RTK Query автоматизирует весь этот процесс.
RTK Query хранит:
Структура внутри Redux store:
{
api: {
queries: {},
mutations: {},
subscriptions: {},
provided: {}
}
}
| Характеристика | Query | Mutation |
|---|---|---|
| Назначение | Получение данных | Изменение данных |
| Кэширование | Да | Нет |
| Автоподписки | Да | Нет |
| Polling | Да | Нет |
| Refetch | Да | Ограниченно |
| Tags | providesTags | invalidatesTags |
| Хранение результата | Долгое | Кратковременное |
Подходит для:
Подходит для:
RTK Query позволяет комбинировать множество возможностей одновременно.
getFeed: builder.query({
query: ({ page }) => ({
url: '/feed',
params: { page }
}),
serializeQueryArgs: ({ endpointName }) => endpointName,
merge: (currentCache, newCache) => {
currentCache.items.push(...newCache.items)
},
forceRefetch({ currentArg, previousArg }) {
return currentArg?.page !== previousArg?.page
},
providesTags: ['Feed'],
keepUnusedDataFor: 300
})
Такой endpoint реализует: