RTK Query позволяет выполнять несколько независимых запросов одновременно в рамках одного компонента, одного дерева компонентов или даже разных частей приложения без ручной координации состояния загрузки, ошибок и кэширования. Параллельность здесь не означает многопоточность в классическом смысле, а представляет собой одновременное инициирование нескольких подписок на разные endpoint’ы с автоматическим управлением жизненным циклом запросов и кэша.
Каждый вызов хука RTK Query формирует отдельную подписку на данные. Ключевая особенность заключается в том, что каждый endpoint идентифицируется комбинацией:
Это означает, что даже внутри одного компонента можно безопасно запускать несколько запросов без риска конфликта состояния.
const users = useGetUsersQuery();
const posts = useGetPostsQuery();
Оба запроса выполняются параллельно. RTK Query не блокирует один запрос другим и не требует ручного orchestration.
Каждый запрос записывается в общий normalized cache Redux Toolkit Query. При этом:
const userA = useGetUserQuery(1);
const userB = useGetUserQuery(1);
Несмотря на два вызова, сеть будет задействована только один раз.
Частый сценарий — параллельная загрузка сущностей по разным идентификаторам.
const user = useGetUserQuery(1);
const comments = useGetCommentsQuery(1);
const posts = useGetUserPostsQuery(1);
Каждый endpoint изолирован, и RTK Query обрабатывает их независимо. При этом система автоматически управляет:
RTK Query позволяет запускать один и тот же endpoint несколько раз с разными параметрами.
const user1 = useGetUserQuery(1);
const user2 = useGetUserQuery(2);
const user3 = useGetUserQuery(3);
Такой подход часто используется при построении dashboard’ов, где данные собираются из разных сущностей одновременно.
skip для управления параллельностьюПараллельные запросы часто зависят от условий. RTK Query
предоставляет механизм skip, позволяющий контролировать
запуск запроса без разрушения структуры хуков.
const { data: user } = useGetUserQuery(1);
const { data: posts } = useGetPostsQuery(user?.id, {
skip: !user?.id
});
Здесь второй запрос фактически становится условно параллельным: он не запускается до получения результата первого.
skipToken
как более строгая альтернативаskipToken позволяет явно указать, что запрос не должен
выполняться.
import { skipToken } from '@reduxjs/toolkit/query';
const userId = user?.id;
const { data: posts } = useGetPostsQuery(userId ?? skipToken);
В отличие от skip, этот подход делает условие частью
аргументов запроса, что улучшает читаемость при сложных цепочках
зависимостей.
Promise.all через initiateХотя RTK Query ориентирован на React hooks, существует возможность
ручного запуска запросов через dispatch.
const promises = [
dispatch(api.endpoints.getUser.initiate(1)),
dispatch(api.endpoints.getPosts.initiate()),
dispatch(api.endpoints.getComments.initiate())
];
const [user, posts, comments] = await Promise.all(promises);
Такой подход полезен в сценариях:
RTK Query при этом сохраняет кэш и дедупликацию.
При одновременном запуске одинаковых запросов RTK Query объединяет их в один сетевой вызов.
const a = useGetUserQuery(1);
const b = useGetUserQuery(1);
const c = useGetUserQuery(1);
Фактически:
Это критически важно для параллельных UI-компонентов, обращающихся к одним данным.
RTK Query работает на уровне store, поэтому запросы в разных местах приложения автоматически синхронизируются.
// Component A
useGetUserQuery(1);
// Component B
useGetUserQuery(1);
Если оба компонента смонтированы одновременно, сеть будет использована один раз.
selectFromResult
для оптимизации параллельных подписокПри большом количестве параллельных запросов важно избегать лишних ререндеров.
const { name } = useGetUserQuery(1, {
selectFromResult: ({ data }) => ({
name: data?.name
})
});
Это позволяет:
При параллельных запросах это особенно важно, так как каждый endpoint может обновляться независимо.
Типичный реальный сценарий — загрузка нескольких доменных сущностей:
const user = useGetUserQuery(1);
const notifications = useGetNotificationsQuery(1);
const settings = useGetSettingsQuery(1);
const permissions = useGetPermissionsQuery(1);
RTK Query выполняет все запросы одновременно, при этом:
В параллельных запросах данные приходят асинхронно. Это приводит к постепенному заполнению UI:
isLoadingconst { data: user, isLoading: userLoading } = useGetUserQuery(1);
const { data: posts, isLoading: postsLoading } = useGetPostsQuery(1);
RTK Query не требует объединения состояний вручную, но позволяет это делать при необходимости.
Часто используется гибридный подход:
const { data: user } = useGetUserQuery(1);
const { data: posts } = useGetPostsQuery(user?.id, {
skip: !user
});
const { data: comments } = useGetCommentsQuery(user?.id, {
skip: !user
});
Здесь:
При масштабировании API часто создаются обертки:
export const useDashboardData = (userId) => {
const user = useGetUserQuery(userId);
const posts = useGetUserPostsQuery(userId);
const stats = useGetUserStatsQuery(userId);
return {
user,
posts,
stats
};
};
Такой паттерн позволяет:
При изменении данных RTK Query может одновременно обновлять несколько кэшей:
updateUser: builder.mutation({
query: (data) => ({
url: `/user/${data.id}`,
method: 'PUT',
body: data
}),
invalidatesTags: ['User', 'Posts']
});
После мутации:
RTK Query поддерживает polling, и несколько polling-запросов могут работать параллельно:
useGetNotificationsQuery(undefined, {
pollingInterval: 5000
});
useGetMessagesQuery(undefined, {
pollingInterval: 3000
});
Каждый endpoint имеет собственный цикл обновления, не блокируя другие.
Параллельные запросы не означают неконтролируемую нагрузку. RTK Query управляет:
Однако архитектурно важно избегать избыточного количества одновременных endpoint’ов без необходимости.
Dashboard загрузка:
const user = useGetUserQuery(id);
const analytics = useGetAnalyticsQuery(id);
const activity = useGetActivityQuery(id);
Страница профиля:
const profile = useGetProfileQuery(id);
const friends = useGetFriendsQuery(id);
const photos = useGetPhotosQuery(id);
Глобальные данные приложения:
const config = useGetConfigQuery();
const session = useGetSessionQuery();
const notifications = useGetNotificationsQuery();
Распространённые проблемы:
skip при зависимых данныхRTK Query уже решает большую часть задач параллельности, поэтому избыточная логика часто приводит к усложнению кода без выигрыша в производительности.
При размонтировании компонента подписка на данные может сохраняться в
кэше в течение времени keepUnusedDataFor.
const api = createApi({
keepUnusedDataFor: 60
});
Это влияет на параллельные запросы следующим образом:
При серверном рендеринге параллельные запросы обычно выполняются
через Promise.all на уровне store:
await Promise.all([
store.dispatch(api.endpoints.getUser.initiate(1)),
store.dispatch(api.endpoints.getPosts.initiate(1))
]);
RTK Query заполняет кэш до рендера, сохраняя структуру параллельных данных для клиента.
RTK Query рассматривает параллельность как совокупность независимых потоков данных:
skip и
skipToken