Архитектура RTK Query построена вокруг концепции декларативного
описания API через createApi, где все эндпоинты фиксируются
в момент создания API-сервиса. Однако в реальных приложениях часто
возникает необходимость динамически расширять API — например, при
модульной архитектуре, code splitting или подключении фичей по
требованию. Для этого используется механизм
injectEndpoints, позволяющий добавлять эндпоинты в уже
существующий API-инстанс.
Вместо того чтобы создавать единый монолитный API-файл, RTK Query позволяет разделять описание эндпоинтов по функциональным модулям. Каждый модуль может «вкалывать» свои эндпоинты в общий API-слой.
Ключевой момент заключается в том, что injectEndpoints
не создаёт новый API, а расширяет существующий. Это означает:
Таким образом, система API становится модульной без потери консистентности состояния.
Метод вызывается на уже созданном API-сервисе:
const extendedApi = api.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users',
}),
}),
});
api — базовый API, созданный через
createApiinjectEndpoints — метод расширенияendpoints — функция, принимающая
builderbuilder.query / builder.mutation — описание
операцийВажно, что возвращаемое значение — новый объект API с добавленными эндпоинтами, но внутренне он связан с исходным сервисом.
Каждый вызов injectEndpoints может добавлять новые
эндпоинты к уже существующим.
const usersApi = api.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users',
}),
}),
});
const postsApi = api.injectEndpoints({
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
}),
}),
});
Оба набора эндпоинтов оказываются в одном API-контексте. Это означает:
Одно из ключевых применений injectEndpoints — разделение
API по чанкам приложения.
api/
baseApi.js
features/
users/
usersApi.js
posts/
postsApi.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: () => ({}),
});
Здесь важно, что endpoints пустой — API создаётся как
контейнер.
import { api } from '../api/baseApi';
export const usersApi = api.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users',
}),
}),
});
export const { useGetUsersQuery } = usersApi;
import { api } from '../api/baseApi';
export const postsApi = api.injectEndpoints({
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
}),
}),
});
export const { useGetPostsQuery } = postsApi;
Несмотря на динамическое добавление эндпоинтов, api
остаётся стабильным объектом. Это критично для React-интеграции:
Даже если injectEndpoints вызывается в разных местах,
результат агрегируется в одном API namespace.
При инъекции возможны конфликты имён эндпоинтов. Для этого
используется параметр overrideExisting.
api.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({
query: () => '/users/v2',
}),
}),
overrideExisting: true,
});
true — перезаписывает существующий эндпоинтfalse (по умолчанию) — вызывает предупреждение или
ошибку в dev-режимеЭто важно при миграциях API или feature toggling.
injectEndpoints часто используется вместе с динамическим
импортом:
const loadUsersApi = async () => {
const module = await import('./usersApi');
return module.usersApi;
};
В этом сценарии эндпоинты регистрируются только при загрузке соответствующего модуля, что уменьшает initial bundle size.
RTK Query автоматически генерирует React hooks для injected endpoints:
export const { useGetUsersQuery } = usersApi;
Особенность injectEndpoints заключается в том, что типы
выводятся из builder-описания, а не из центрального API-файла. Это
позволяет:
Несмотря на модульное расширение, все injected endpoints работают с единой системой кэширования:
baseQueryreducerPathЭто означает, что запросы из разных модулей могут использовать одинаковые данные без повторных запросов.
Пример:
getUsers в usersApigetUserById в postsApi или другом модулеЕсли queryKey совпадает логически, RTK Query использует кэш повторно.
Несмотря на гибкость, механизм имеет ряд архитектурных ограничений:
После создания createApi нельзя изменить:
injectEndpoints работает только на уровне
эндпоинтов.
Все injected endpoints должны принадлежать одному API. Нельзя объединить несколько разных createApi через inject.
При масштабной модульной структуре требуется строгая договорённость по неймингам:
Наиболее устойчивый подход — привязка injectEndpoints к feature-слою:
features/
auth/
authApi.js
profile/
profileApi.js
dashboard/
dashboardApi.js
Каждый модуль:
Это создаёт слабую связанность между частями API.
Базовый store конфигурируется один раз:
import { configureStore } from '@reduxjs/toolkit';
import { api } from './api/baseApi';
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
});
После этого любые injectEndpoints автоматически начинают
работать без дополнительной настройки store.
При необходимости endpoints могут добавляться уже после инициализации приложения:
store.dispatch(
api.util.resetApiState()
);
или через side-effect загрузку модулей:
await import('./features/comments/commentsApi');
После импорта endpoints становятся доступными в API-контексте без перезагрузки store.
Redux DevTools отображают injected endpoints так же, как и статические:
Однако при большом количестве inject-модулей важно контролировать:
Механизм injectEndpoints фактически превращает RTK Query
в:
Он позволяет уходить от централизованных API-файлов в сторону распределённой архитектуры, где каждый feature владеет своими запросами, но использует общий кэш и транспортный слой.