HTTP-заголовки позволяют передавать дополнительную информацию вместе с запросом. В RTK Query механизм добавления заголовков используется для:
RTK Query предоставляет несколько способов добавления заголовков:
prepareHeaders;baseQuery.prepareHeadersНаиболее распространённый способ — использование функции
prepareHeaders внутри fetchBaseQuery.
Пример базовой конфигурации:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.example.com',
prepareHeaders: (headers) => {
headers.set('Content-Type', 'application/json')
headers.set('Accept', 'application/json')
return headers
}
}),
endpoints: () => ({})
})
В данном случае заголовки будут автоматически добавляться ко всем запросам API.
Чаще всего заголовки используются для передачи JWT-токена.
Пример:
prepareHeaders: (headers) => {
const token = localStorage.getItem('token')
if (token) {
headers.set('Authorization', `Bearer ${token}`)
}
return headers
}
После этого каждый запрос будет содержать:
Authorization: Bearer eyJhbGciOi...
RTK Query позволяет получать доступ к состоянию Redux внутри
prepareHeaders.
Пример:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.example.com',
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token
if (token) {
headers.set('Authorization', `Bearer ${token}`)
}
return headers
}
}),
endpoints: () => ({})
})
prepareHeadersФункция prepareHeaders получает объект с дополнительной
информацией:
prepareHeaders: (headers, api) => {
console.log(api)
return headers
}
Доступные свойства:
{
getState,
endpoint,
type,
forced,
extra
}
endpointИмя текущего endpoint:
prepareHeaders: (headers, { endpoint }) => {
console.log(endpoint)
return headers
}
typeТип запроса:
prepareHeaders: (headers, { type }) => {
console.log(type)
}
Возможные значения:
'query'
'mutation'
forcedПоказывает, был ли запрос принудительно обновлён.
prepareHeaders: (headers, { forced }) => {
console.log(forced)
return headers
}
Иногда разные endpoint требуют разные заголовки.
Пример:
prepareHeaders: (headers, { endpoint }) => {
if (endpoint === 'uploadFile') {
headers.set('X-Upload-Mode', 'true')
}
return headers
}
Многие внешние сервисы используют API-ключи.
Пример:
prepareHeaders: (headers) => {
headers.set('X-API-Key', 'my-secret-key')
return headers
}
API может требовать произвольные заголовки.
Пример:
prepareHeaders: (headers) => {
headers.set('X-App-Version', '1.0.0')
headers.set('X-Client', 'web')
return headers
}
Некоторые backend-сервисы поддерживают мультиязычность через
заголовок Accept-Language.
Пример:
prepareHeaders: (headers) => {
headers.set('Accept-Language', 'ru')
return headers
}
Заголовки можно указывать непосредственно внутри запроса.
Пример:
getUsers: builder.query({
query: () => ({
url: '/users',
headers: {
'X-Request-Source': 'admin-panel'
}
})
})
Заголовки могут зависеть от аргументов запроса.
Пример:
getUser: builder.query({
query: (id) => ({
url: `/users/${id}`,
headers: {
'X-User-ID': id
}
})
})
RTK Query объединяет заголовки из prepareHeaders и
endpoint.
Пример:
baseQuery: fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers) => {
headers.set('Authorization', 'Bearer token')
return headers
}
})
Endpoint:
getPosts: builder.query({
query: () => ({
url: '/posts',
headers: {
'X-Module': 'blog'
}
})
})
Результирующий запрос:
Authorization: Bearer token
X-Module: blog
Локальные заголовки могут переопределять глобальные.
Пример:
prepareHeaders: (headers) => {
headers.set('Content-Type', 'application/json')
return headers
}
Endpoint:
uploadFile: builder.mutation({
query: (formData) => ({
url: '/upload',
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
})
FormDataПри использовании FormData важно учитывать особенности
браузера.
Неправильный вариант:
headers.set('Content-Type', 'multipart/form-data')
Браузер должен самостоятельно формировать boundary.
Правильный вариант:
uploadFile: builder.mutation({
query: (formData) => ({
url: '/upload',
method: 'POST',
body: formData
})
})
Объект headers поддерживает метод
delete.
Пример:
prepareHeaders: (headers, { endpoint }) => {
if (endpoint === 'publicData') {
headers.delete('Authorization')
}
return headers
}
Метод has позволяет проверить наличие заголовка.
Пример:
prepareHeaders: (headers) => {
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
return headers
}
Пример:
prepareHeaders: (headers) => {
console.log(headers.get('Authorization'))
return headers
}
Для работы с cookie необходимо включить credentials.
Пример:
baseQuery: fetchBaseQuery({
baseUrl: '/api',
credentials: 'include'
})
Теперь браузер будет автоматически отправлять cookie вместе с запросами.
credentialsВозможные значения:
'omit'
'same-origin'
'include'
Пример:
baseQuery: fetchBaseQuery({
baseUrl: '/api',
credentials: 'same-origin'
})
Пример:
createPost: builder.mutation({
query: (post) => ({
url: '/posts',
method: 'POST',
body: post,
headers: {
'X-Action': 'create-post'
}
})
})
Пример:
getPosts: builder.query({
query: () => ({
url: '/posts',
headers: {
'Cache-Control': 'no-cache'
}
})
})
Крупные приложения обычно выносят логику заголовков в отдельные функции.
Пример:
const setAuthHeaders = (headers, token) => {
if (token) {
headers.set('Authorization', `Bearer ${token}`)
}
return headers
}
Использование:
prepareHeaders: (headers, { getState }) => {
const token = getState().auth.token
return setAuthHeaders(headers, token)
}
Иногда приложение использует разные токены для разных сервисов.
Пример:
prepareHeaders: (headers, { endpoint, getState }) => {
const state = getState()
if (endpoint.startsWith('admin')) {
headers.set(
'Authorization',
`Bearer ${state.admin.token}`
)
} else {
headers.set(
'Authorization',
`Bearer ${state.user.token}`
)
}
return headers
}
baseQueryДля сложной логики можно создать собственный
baseQuery.
Пример:
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
const rawBaseQuery = fetchBaseQuery({
baseUrl: '/api'
})
const customBaseQuery = async (args, api, extraOptions) => {
const token = api.getState().auth.token
if (typeof args === 'string') {
args = {
url: args
}
}
args.headers = {
...args.headers,
Authorization: `Bearer ${token}`
}
return rawBaseQuery(args, api, extraOptions)
}
Использование:
export const api = createApi({
reducerPath: 'api',
baseQuery: customBaseQuery,
endpoints: () => ({})
})
Часто baseQuery используется для обновления access token
после получения ошибки 401.
Пример структуры:
const baseQuery = fetchBaseQuery({
baseUrl: '/api'
})
const baseQueryWithReauth = async (
args,
api,
extraOptions
) => {
let result = await baseQuery(args, api, extraOptions)
if (result.error?.status === 401) {
const refreshResult = await baseQuery(
'/refresh',
api,
extraOptions
)
if (refreshResult.data) {
api.dispatch(setToken(refreshResult.data.token))
result = await baseQuery(
args,
api,
extraOptions
)
}
}
return result
}
return headersНеправильный вариант:
prepareHeaders: (headers) => {
headers.set('Authorization', 'Bearer token')
}
Правильный вариант:
prepareHeaders: (headers) => {
headers.set('Authorization', 'Bearer token')
return headers
}
Плохая практика:
headers.set('Authorization', 'Bearer 123456')
Токен должен храниться:
multipart/form-dataОдна из самых распространённых ошибок:
headers.set('Content-Type', 'multipart/form-data')
Это приводит к отсутствию boundary и поломке загрузки файлов.
Обычно создаётся единый API-модуль:
export const api = createApi({
reducerPath: 'api',
baseQuery,
endpoints: () => ({})
})
Пример:
prepareHeaders: (headers, { endpoint }) => {
const publicEndpoints = [
'login',
'register'
]
if (!publicEndpoints.includes(endpoint)) {
headers.set('Authorization', 'Bearer token')
}
return headers
}
Пример структуры:
src/
├── app/
├── services/
│ ├── api.js
│ ├── baseQuery.js
│ └── headers.js
Такой подход упрощает поддержку и повторное использование логики.