Параметр baseQuery является фундаментальной частью API
RTK Query. Именно через него выполняются HTTP-запросы, обрабатываются
ответы сервера, формируются ошибки, добавляются заголовки, токены
авторизации и реализуется любая кастомная логика взаимодействия с
backend.
baseQuery указывается внутри функции
createApi:
import { createApi, fetchBaseQuery } fr om '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.example.com',
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
}),
}),
});
Без baseQuery RTK Query не знает, каким образом
выполнять сетевые запросы.
baseQuery в архитектуре RTK QuerybaseQuery выполняет несколько важных задач:
Каждый endpoint внутри endpoints использует единый
baseQuery, если явно не указано иное.
Архитектурно это выглядит следующим образом:
Component
↓
Hook RTK Query
↓
Endpoint
↓
baseQuery
↓
HTTP request
↓
Server
fetchBaseQueryRTK Query поставляется со встроенным fetchBaseQuery.
Это небольшая обёртка над стандартным браузерным API
fetch.
Подключение:
import { fetchBaseQuery } from '@reduxjs/toolkit/query/react';
Пример:
baseQuery: fetchBaseQuery({
baseUrl: 'https://jsonplaceholder.typicode.com',
})
После этого endpoint может использовать относительные URL:
getUsers: builder.query({
query: () => '/users',
})
Фактический адрес запроса:
https://jsonplaceholder.typicode.com/users
baseUrlНаиболее распространённая настройка.
fetchBaseQuery({
baseUrl: 'https://api.site.com',
})
Позволяет избежать повторения одинаковых частей URL.
Без baseUrl:
query: () => 'https://api.site.com/posts'
С baseUrl:
query: () => '/posts'
Функция query может возвращать:
query: () => '/posts'
RTK Query автоматически выполнит GET-запрос.
query: () => ({
url: '/posts',
method: 'POST',
body: {
title: 'New post',
},
})
Полная конфигурация:
query: () => ({
url,
method,
body,
params,
headers,
credentials,
responseHandler,
})
Поддерживаются любые HTTP-методы.
query: () => ({
url: '/posts',
method: 'GET',
})
query: (data) => ({
url: '/posts',
method: 'POST',
body: data,
})
query: ({ id, ...body }) => ({
url: `/posts/${id}`,
method: 'PUT',
body,
})
query: ({ id, ...body }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body,
})
query: (id) => ({
url: `/posts/${id}`,
method: 'DELETE',
})
Параметр params автоматически сериализуется в query
string.
query: () => ({
url: '/posts',
params: {
page: 1,
lim it: 10,
},
})
Результат:
/posts?page=1&limit=10
fetchBaseQuery автоматически сериализует объект в
JSON.
query: (user) => ({
url: '/users',
method: 'POST',
body: user,
})
Фактически:
JSON.stringify(user)
Также автоматически устанавливается заголовок:
Content-Type: application/json
query: () => ({
url: '/profile',
headers: {
Authorization: 'Bearer token',
},
})
prepareHeadersНаиболее важный механизм настройки baseQuery.
Используется для:
Пример:
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.site.com',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
})
prepareHeadersВторой аргумент содержит полезную информацию:
prepareHeaders: (headers, api) => {
console.log(api);
}
Доступны:
{
getState,
endpoint,
type,
forced,
extra
}
getStateПолучение данных из Redux Store.
prepareHeaders: (headers, { getState }) => {
const state = getState();
headers.set('X-Language', state.settings.language);
return headers;
}
endpointПозволяет определять текущий endpoint.
prepareHeaders: (headers, { endpoint }) => {
if (endpoint === 'uploadAvatar') {
headers.set('X-Upload', 'true');
}
return headers;
}
Наиболее распространённый сценарий.
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.accessToken;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
})
По умолчанию fetch не отправляет cookie между
доменами.
Для включения:
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.site.com',
credentials: 'include',
})
Возможные значения:
omit
same-origin
include
RTK Query автоматически пытается распарсить JSON.
Например:
{
"id": 1,
"title": "Post"
}
преобразуется в JavaScript-объект.
Позволяет вручную управлять обработкой ответа.
query: () => ({
url: '/text',
responseHandler: 'text',
})
query: () => ({
url: '/file',
responseHandler: (response) => response.blob(),
})
query: () => ({
url: '/data',
responseHandler: async (response) => {
const text = await response.text();
return text.toUpperCase();
},
})
При ошибках RTK Query формирует объект:
{
error: {
status,
data
}
}
Пример:
{
status: 404,
data: {
message: 'Not found'
}
}
400
401
403
404
500
{
status: 'PARSING_ERROR'
}
{
status: 'FETCH_ERROR'
}
{
status: 'TIMEOUT_ERROR'
}
baseQuery: fetchBaseQuery({
baseUrl: '/api',
timeout: 5000,
})
Если сервер не ответил за 5 секунд:
{
error: {
status: 'TIMEOUT_ERROR'
}
}
baseQueryRTK Query позволяет полностью заменить
fetchBaseQuery.
Сигнатура:
const customBaseQuery = async (
args,
api,
extraOptions
) => {
return {
data,
};
// или
return {
error,
};
};
argsАргументы запроса.
apiСлужебные методы RTK Query.
extraOptionsДополнительные параметры.
const customBaseQuery = async (args) => {
try {
const response = await fetch(args.url);
const data = await response.json();
return { data };
} catch (error) {
return {
error: {
status: 'CUSTOM_ERROR',
data: error,
},
};
}
};
RTK Query не зависит от fetch.
Можно использовать Axios.
import axios from 'axios';
const axiosBaseQuery =
({ baseUrl }) =>
async ({ url, method, data, params }) => {
try {
const result = await axios({
url: baseUrl + url,
method,
data,
params,
});
return {
data: result.data,
};
} catch (axiosError) {
return {
error: {
status: axiosError.response?.status,
data: axiosError.response?.data,
},
};
}
};
Подключение:
baseQuery: axiosBaseQuery({
baseUrl: '/api',
})
Один из самых важных сценариев custom baseQuery.
Схема:
const baseQuery = fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
return headers;
},
});
const baseQueryWithReauth = async (
args,
api,
extraOptions
) => {
let result = await baseQuery(
args,
api,
extraOptions
);
if (result.error?.status === 401) {
const refreshResult = await baseQuery(
{
url: '/refresh',
method: 'POST',
},
api,
extraOptions
);
if (refreshResult.data) {
api.dispatch(
setCredentials(refreshResult.data)
);
result = await baseQuery(
args,
api,
extraOptions
);
} else {
api.dispatch(logout());
}
}
return result;
};
api предоставляет доступ к Redux dispatch.
api.dispatch(action())
Пример:
api.dispatch(logout())
const state = api.getState();
Пример:
const locale = api.getState().settings.locale;
const loggingBaseQuery = async (
args,
api,
extraOptions
) => {
console.log('Request:', args);
const result = await baseQuery(
args,
api,
extraOptions
);
console.log('Response:', result);
return result;
};
const baseQueryWithErrors = async (
args,
api,
extraOptions
) => {
const result = await baseQuery(
args,
api,
extraOptions
);
if (result.error?.status === 500) {
api.dispatch(showServerError());
}
return result;
};
const dynamicBaseQuery = async (
args,
api,
extraOptions
) => {
const state = api.getState();
const tenantId = state.auth.tenantId;
args.params = {
...args.params,
tenantId,
};
return baseQuery(args, api, extraOptions);
};
RTK Query может использовать GraphQL вместо REST.
import { request } from 'graphql-request';
const graphqlBaseQuery =
({ baseUrl }) =>
async ({ body }) => {
try {
const result = await request(
baseUrl,
body
);
return { data: result };
} catch (error) {
return {
error: {
status: error.response.status,
data: error,
},
};
}
};
Иногда endpoint полностью переопределяет механизм запроса.
getUser: builder.query({
async queryFn(id) {
try {
const response = await fetch(`/users/${id}`);
const data = await response.json();
return { data };
} catch (error) {
return { error };
}
},
})
В этом случае baseQuery не используется.
query и baseQueryqueryОписывает конкретный endpoint.
query: (id) => `/users/${id}`
baseQueryОпределяет механизм выполнения всех запросов.
baseQuery: fetchBaseQuery({
baseUrl: '/api',
})
Можно создавать цепочки.
baseQuery
→ auth wrapper
→ logging wrapper
→ retry wrapper
→ final request
RTK Query содержит встроенный retry wrapper.
import {
retry,
fetchBaseQuery,
} from '@reduxjs/toolkit/query/react';
Пример:
const staggeredBaseQuery = retry(
fetchBaseQuery({
baseUrl: '/api',
}),
{
maxRetries: 5,
}
);
baseQuery поддерживает AbortController.
const customBaseQuery = async (
args,
api
) => {
const response = await fetch(args.url, {
signal: api.signal,
});
return {
data: await response.json(),
};
};
const customBaseQuery = async (
args,
api
) => {
console.log(api.endpoint);
};
console.log(api.type);
Значения:
query
mutation
Дополнительные параметры endpoint.
getUsers: builder.query({
query: () => '/users',
extraOptions: {
retry: false,
},
})
Получение:
const customBaseQuery = async (
args,
api,
extraOptions
) => {
console.log(extraOptions.retry);
};
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const token =
getState().auth.token;
if (token) {
headers.set(
'Authorization',
`Bearer ${token}`
);
}
return headers;
},
}),
tagTypes: ['Posts'],
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
}),
addPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body,
}),
}),
}),
});
baseQueryprepareHeaders
baseQueryWithReauth
baseUrl
tenant headers
custom fetch implementation
graphqlBaseQuery
axiosBaseQuery
retry(fetchBaseQuery())
baseQueryНеправильно:
prepareHeaders: (headers) => {
headers.set('Authorization', 'token');
}
Правильно:
prepareHeaders: (headers) => {
headers.set('Authorization', 'token');
return headers;
}
Неправильно:
return {
message: 'error'
}
Правильно:
return {
error: {
status: 500,
data: 'error',
},
}
Неправильно:
throw error;
Правильно:
return {
error,
};
baseQueryfetchBaseQuery специально реализован минималистично:
По этой причине fetchBaseQuery рекомендуется
использовать по умолчанию, если проект не требует сложной
HTTP-инфраструктуры.