Архитектура RTK Query построена вокруг идеи декларативного описания
API через единый apiSlice, который создаётся с помощью
createApi. Однако в реальных приложениях часто возникает
необходимость расширять API после его первоначального определения:
подключать новые наборы запросов в зависимости от фичи, разделять код по
модулям или подгружать функциональность лениво. Для этого используется
механизм динамического расширения через
injectEndpoints.
Функция injectEndpoints позволяет добавлять новые
endpoints в уже существующий API-слайс без его пересоздания. Это
ключевой механизм для модульной архитектуры и code splitting.
Основная идея:
apiSliceinjectEndpointsЭто позволяет избегать монолитного описания API и поддерживать масштабируемость.
Перед использованием динамического расширения создаётся основной API:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const baseApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
}),
endpoints: () => ({}),
});
Ключевая деталь — endpoints: () => ({}). Пустое
описание endpoints означает, что базовый слой только готовит
инфраструктуру для дальнейшего расширения.
Динамическое расширение происходит следующим образом:
import { baseApi } from './baseApi';
export const extendedApi = baseApi.injectEndpoints({
endpoints: (build) => ({
getUsers: build.query({
query: () => '/users',
}),
}),
overrideExisting: false,
});
endpoints: функция, принимающая build и
возвращающая объект endpointsbuild.query: описание GET-подобного запросаbuild.mutation: описание изменений состояния на
сервереoverrideExisting: управление конфликтами имёнЧасто injectEndpoints используется без создания нового
API-объекта. Сам вызов достаточно выполнить один раз, чтобы
зарегистрировать endpoints глобально.
// usersApi.js
import { baseApi } from './baseApi';
baseApi.injectEndpoints({
endpoints: (build) => ({
getUsers: build.query({
query: () => '/users',
}),
}),
});
После выполнения этого кода endpoint становится доступен через
baseApi.
В крупных приложениях endpoints группируются по функциональным зонам:
import { baseApi } from '../baseApi';
export const usersApi = baseApi.injectEndpoints({
endpoints: (build) => ({
getUserById: build.query({
query: (id) => `/users/${id}`,
}),
updateUser: build.mutation({
query: (body) => ({
url: `/users/${body.id}`,
method: 'PUT',
body,
}),
}),
}),
});
import { baseApi } from '../baseApi';
export const postsApi = baseApi.injectEndpoints({
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
}),
}),
});
Такой подход формирует логическую изоляцию без создания отдельных API-инстансов.
RTK Query автоматически генерирует хуки для каждого endpoint. При динамическом добавлении это работает аналогично статическим endpoints.
export const {
useGetUsersQuery,
useUpdateUserMutation,
} = usersApi;
Важно учитывать, что хуки доступны только после того, как модуль с
injectEndpoints был импортирован хотя бы один раз.
Динамическое добавление особенно полезно в связке с lazy loading:
import React, { useEffect } from 'react';
import { baseApi } from './baseApi';
export function UsersPage() {
useEffect(() => {
import('./usersApi');
}, []);
const { data } = baseApi.useGetUsersQuery();
return (
<div>
{data?.map((u) => (
<div key={u.id}>{u.name}</div>
))}
</div>
);
}
Здесь:
usersApi подгружается только при открытии страницыПри повторной инъекции endpoint с тем же именем возникает конфликт.
Управление происходит через overrideExisting.
false — ошибка или игнорирование повторного
определенияtrue — перезапись существующего endpointbaseApi.injectEndpoints({
endpoints: (build) => ({
getUsers: build.query({
query: () => '/users-v2',
}),
}),
overrideExisting: true,
});
Этот механизм используется при версионировании API или feature flags.
injectEndpoints позволяет расширять не только запросы, но и поведение кэширования, теги и инвалидацию.
baseApi.injectEndpoints({
endpoints: (build) => ({
addUser: build.mutation({
query: (user) => ({
url: '/users',
method: 'POST',
body: user,
}),
invalidatesTags: ['Users'],
}),
}),
});
При этом важно, что теговая система остаётся общей для всего API-слайса.
Один baseApi становится центральной точкой регистрации
всех endpoints. Модули могут импортировать его независимо друг от
друга.
// module A
baseApi.injectEndpoints({ ... });
// module B
baseApi.injectEndpoints({ ... });
RTK Query гарантирует объединение всех endpoints в единый registry.
Кэш RTK Query не зависит от способа добавления endpoint. Однако есть важный момент:
Это критично при lazy loading: порядок импорта влияет на доступность данных.
Часто вместо injectEndpoints создают несколько
createApi. Это приводит к:
Правильный подход — один baseApi + injectEndpoints.
Если модуль с injectEndpoints не загружен, hook не существует:
useXxxQuery is not a functionРешается явным импортом или lazy import на уровне маршрутизации.
При одинаковых именах endpoints:
Рекомендуется использовать нейминг по доменам:
getUserById
users/getById
auth/login
Хотя пример приведён в JavaScript, динамические endpoints также поддерживают типизацию. Типы автоматически расширяются при корректной организации модулей, но требуют аккуратной структуры импортов, чтобы избежать расхождения типов и runtime-реальности.
После добавления endpoints через injectEndpoints:
Это обеспечивает стабильность store даже при динамическом расширении API.
Наиболее естественное применение injectEndpoints — разбиение по фичам:
/features
/users
usersApi.js
usersSlice.js
/posts
postsApi.js
postsSlice.js
Каждый модуль самостоятельно расширяет baseApi, не зная о других частях системы.