Гидратация состояния в RTK Query представляет собой механизм восстановления кэша запросов на клиенте после серверного рендеринга или после передачи предзагруженного состояния Redux. Основная задача этого процесса — синхронизировать данные, полученные на сервере, с клиентским хранилищем без повторных запросов и потери актуальности уже загруженных сущностей.
RTK Query хранит данные в нормализованном виде внутри Redux store. Каждый запрос идентифицируется комбинацией:
Кэш включает:
pending, fulfilled,
rejected)При SSR (Server-Side Rendering) формируется предварительно заполненный Redux store, который затем передаётся на клиент. Именно в этот момент возникает необходимость гидратации: клиент должен корректно объединить серверное состояние с собственным экземпляром store.
Без гидратации возникают типичные проблемы:
RTK Query решает эти проблемы через механизм rehydration, который позволяет “влить” серверный кэш в клиентский store без нарушения целостности уже существующих данных.
В основе лежит Redux action, который передаёт сериализованное состояние store:
const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware),
preloadedState: window.__PRELOADED_STATE__,
});
На сервере формируется preloadedState, содержащий кэш
RTK Query. На клиенте этот объект используется как начальное состояние
Redux.
Однако этого недостаточно, так как RTK Query требует корректного объединения (merge) внутренних структур кэша.
В экосистеме Next.js часто используется
next-redux-wrapper, который вводит специальный action:
import { HYDRATE } from 'next-redux-wrapper';
Этот action применяется для объединения серверного и клиентского состояния:
const rootReducer = (state, action) => {
switch (action.type) {
case HYDRATE:
return {
...state,
...action.payload,
};
default:
return appReducer(state, action);
}
};
Но при работе с RTK Query такой поверхностный merge недостаточен, поскольку кэш API имеет вложенную структуру с ключами запросов и метаданными.
RTK Query предоставляет встроенный механизм для корректной гидратации
через extractRehydrationInfo.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === HYDRATE) {
return action.payload[reducerPath];
}
},
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
}),
}),
});
Этот механизм выполняет ключевую функцию:
Состояние RTK Query обычно выглядит так:
{
queries: {
"getPosts(undefined)": {
status: "fulfilled",
endpointName: "getPosts",
data: [...],
fulfilledTimeStamp: 123456789
}
},
mutations: {},
provided: {},
subscriptions: {},
config: {}
}
При гидратации важно, чтобы:
queries не перетирались полностьюRTK Query использует стратегию глубокого объединения состояния API slice. При гидратации:
Особенно важно поведение при конфликте:
fulfilled,
серверный результат может быть проигнорированКлючевым элементом корректной работы является сериализация аргументов запроса:
serializeQueryArgs: ({ endpointName, queryArgs }) => {
return `${endpointName}/${JSON.stringify(queryArgs)}`;
};
При SSR и hydration важно, чтобы:
Несовпадение сериализации приводит к эффекту “дублирования кэша”, когда данные существуют дважды в store.
Lazy queries добавляют дополнительную сложность. При SSR:
RTK Query хранит информацию о подписках, поэтому при гидратации:
Типичный поток:
dispatch(api.endpoints.getPosts.initiate())awaitstore.getState()await store.dispatch(api.endpoints.getPosts.initiate());
await Promise.all(store.dispatch(api.util.getRunningQueriesThunk()));
После этого:
const preloadedState = store.getState();
На клиенте этот state становится источником гидратации.
Этот thunk обеспечивает завершение всех активных запросов перед сериализацией состояния. Без него возможны:
pendingawait Promise.all(store.dispatch(api.util.getRunningQueriesThunk()));
После восстановления состояния RTK Query применяет стандартные политики:
refetchOnMountOrArgChangerefetchOnFocusrefetchOnReconnectГидратация не блокирует эти механизмы, а лишь снижает вероятность немедленного повторного запроса.
Если данные:
Возможны ситуации, когда:
RTK Query решает это через timestamp-based reconciliation:
fulfilledTimeStamp определяет актуальностьГидратация не отключает механизм tag invalidation. После восстановления:
providesTags остаются активнымиinvalidateTags может инициировать refetchЭто важно для динамических систем, где SSR даёт только стартовое состояние, а актуальность обеспечивается клиентом.
При использовании нескольких createApi slices:
extractRehydrationInfo должен учитывать
reducerPathif (action.type === HYDRATE) {
return action.payload[reducerPath];
}
Ошибки в этом месте приводят к:
RTK Query хранит значительный объём метаданных. При гидратации важно учитывать:
api.util.resetApiState()Также полезна стратегия selective prefetch:
extractRehydrationInfogetRunningQueriesThunkКаждая из этих ошибок приводит либо к лишним запросам, либо к потере кэша, либо к рассинхронизации UI.
RTK Query поддерживает частичную гидратацию:
Это поведение используется в гибридных SSR/CSR приложениях, где сервер рендерит только ключевые данные страницы.