В RTK Query каждый запрос проходит строго определённую цепочку
обработки: формирование аргументов, выполнение baseQuery,
получение ответа и его последующая нормализация перед попаданием в кэш.
Именно на этапе постобработки ответа применяется
transformResponse.
transformResponse позволяет изменить структуру данных,
возвращаемых сервером, до того как они будут сохранены в Redux-кэше и
переданы в компоненты через автоматически сгенерированные хуки.
Ключевая идея заключается в том, что слой API-ответа не обязан совпадать с тем, как данные должны выглядеть в приложении.
transformResponse — это функция, которая получает:
baseQueryи возвращает преобразованное значение, которое становится итоговым
data в RTK Query.
Общий вид:
transformResponse: (response, meta, arg) => {
return response;
}
Первый аргумент — фактический ответ сервера. Второй и третий используются при необходимости учитывать HTTP-метаданные или параметры запроса.
Часто сервер возвращает данные в обёртке:
{
"data": {
"items": [1, 2, 3],
"total": 3
},
"status": "ok"
}
В приложении обычно требуется только items.
Использование transformResponse:
getUsers: builder.query({
query: () => '/users',
transformResponse: (response) => {
return response.data.items;
}
});
После этого в компоненте data будет сразу массив, без
промежуточных обёрток.
RTK Query не навязывает нормализацию данных, но
transformResponse часто используется для приведения
структуры к удобному виду.
Пример нормализации:
transformResponse: (response) => {
return response.users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
}
Результат — объект с доступом по идентификатору:
{
"1": { id: 1, name: "Alex" },
"2": { id: 2, name: "Anna" }
}
Это упрощает дальнейшую работу с кэшем и снижает необходимость дополнительной обработки в компонентах.
В некоторых случаях требуется учитывать HTTP-уровень ответа:
transformResponse: (response, meta) => {
if (meta?.response?.status === 204) {
return [];
}
return response.data;
}
Здесь meta содержит информацию о сетевом ответе, включая
статус-код и заголовки (в зависимости от baseQuery).
Это позволяет централизованно обрабатывать нестандартные ответы API.
Третий параметр arg позволяет учитывать параметры
запроса при трансформации.
Пример:
getPosts: builder.query({
query: (limit) => `/posts?limit=${limit}`,
transformResponse: (response, meta, arg) => {
if (arg > 10) {
return response.data.slice(0, 10);
}
return response.data;
}
});
Здесь логика трансформации зависит от входного параметра
limit.
Важно разграничивать обязанности:
baseQuery отвечает за получение данных (fetch, axios и
т.д.)transformResponse отвечает за структуру данных в
приложенииНеправильный подход — пытаться в baseQuery выполнять
бизнес-трансформации. Это усложняет повторное использование и
тестирование.
Правильная архитектура:
baseQuery — транспортный уровеньtransformResponse — слой адаптации API под
приложениеRTK Query позволяет комбинировать несколько уровней преобразования данных:
baseQuery получает сырой ответtransformResponse преобразует структуруselectFromResult дополнительно фильтрует данные на
уровне хукаПример:
transformResponse: (response) => response.data.items
и далее в компоненте:
useGetUsersQuery(undefined, {
selectFromResult: ({ data }) => ({
activeUsers: data?.filter(u => u.active)
})
});
Таким образом, transformResponse задаёт глобальный
формат, а selectFromResult — локальную оптимизацию.
Некоторые API возвращают глубоко вложенные структуры:
{
"payload": {
"result": {
"users": [...]
}
}
}
transformResponse позволяет централизованно упростить
доступ:
transformResponse: (response) => {
return response.payload.result.users;
}
Это снижает связанность UI с форматом API.
Частый кейс — приведение строковых значений к нужным типам:
transformResponse: (response) => {
return response.map(item => ({
...item,
createdAt: new Date(item.createdAt),
price: Number(item.price)
}));
}
Такой подход избавляет компоненты от необходимости выполнять преобразования при каждом рендере.
Фильтрация также является частью трансформации:
transformResponse: (response) => {
return response.data.filter(item => item.isActive);
}
Это позволяет исключить невалидные или неиспользуемые данные до попадания в кэш RTK Query.
Одно из ключевых преимуществ transformResponse —
изоляция UI от изменений API.
Если сервер изменит структуру:
{
"result": [...]
}
вместо:
{
"data": [...]
}
достаточно обновить только одну функцию:
transformResponse: (response) => response.result;
Компоненты и хуки при этом остаются неизменными.
Несмотря на гибкость, transformResponse имеет важные
особенности:
Любая асинхронная логика должна быть вынесена за пределы RTK Query.
Преобразованные данные становятся частью кэша RTK Query. Это означает, что:
transformResponseПример:
transformResponse: (response) => response.items
и затем изменение на:
transformResponse: (response) => response.data.items
фактически меняет контракт кэша.
Хотя логика остаётся идентичной, transformResponse часто
используется для приведения типов к более удобным формам:
null-полейДаже в JavaScript-ориентированных проектах это позволяет поддерживать единообразие структуры данных.
В реальных проектах часто применяется многоуровневая обработка:
transformResponse: (response) => {
const items = response.data.items;
return items
.filter(item => item.isActive)
.map(item => ({
id: item.id,
title: item.title.trim(),
createdAt: new Date(item.createdAt)
}));
}
Такой подход объединяет извлечение, фильтрацию и нормализацию в одном месте, что делает слой API предсказуемым и централизованным.
На практике часто встречаются следующие проблемы:
Оптимальный подход — сохранять transformResponse как
чистую функцию преобразования данных без побочных эффектов и внешних
зависимостей.