TanStack Query решает задачу управления серверным состоянием — данными, которые находятся вне приложения и синхронизируются через HTTP, WebSocket или другие транспортные механизмы.
К серверному состоянию относятся:
Главная особенность библиотеки заключается в том, что она не пытается заменить глобальное состояние приложения полностью. TanStack Query концентрируется именно на асинхронных данных и их жизненном цикле.
Это принципиально отличает её от:
Перед сравнением необходимо разделять два типа состояния.
Хранится исключительно внутри приложения:
const [theme, setTheme] = useState('dark');
Примеры:
Приходит извне:
const response = await fetch('/api/users');
Особенности серверного состояния:
Именно с этим типом данных работает TanStack Query.
Redux — централизованное хранилище состояния.
Типичная схема работы:
Классический Redux требует большого количества инфраструктурного кода.
const fetchUsers = () => async (dispatch) => {
dispatch({ type: 'USERS_LOADING' });
try {
const response = await fetch('/api/users');
const data = await response.json();
dispatch({
type: 'USERS_SUCCESS',
payload: data
});
} catch (error) {
dispatch({
type: 'USERS_ERROR',
payload: error
});
}
};
Дополнительно потребуются:
const usersQuery = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
return response.json();
}
});
TanStack Query автоматически предоставляет:
Для одного endpoint часто требуются:
Обычно достаточно:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
});
Разница особенно заметна в крупных приложениях с большим количеством API-запросов.
Redux не содержит встроенного кэша.
Разработчику приходится самостоятельно:
Кэш встроен изначально.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 60000
});
Библиотека автоматически:
После mutation приходится вручную обновлять store.
dispatch(updatePost(post));
dispatch(fetchPosts());
queryClient.invalidateQueries({
queryKey: ['posts']
});
Инвалидация становится декларативной и централизованной.
Redux традиционно использует нормализованную структуру:
{
users: {
byId: {},
allIds: []
}
}
TanStack Query обычно хранит данные в виде отдельных query-кэшей.
Это упрощает архитектуру и уменьшает количество преобразований.
Появление RTK Query стало ответом на проблемы классического Redux при работе с серверным состоянием.
RTK Query заимствует множество идей TanStack Query:
Необходим store:
configureStore({
reducer: {
[api.reducerPath]: api.reducer
}
});
<QueryClientProvider client={queryClient}>
Нет необходимости строить полноценную Redux-инфраструктуру.
Redux остаётся актуальным для:
TanStack Query не предназначен для замены всего state management.
Context API предназначен для передачи данных через дерево компонентов.
<ThemeContext.Provider value={theme}>
Это не полноценное решение для серверного состояния.
Context не предоставляет:
При изменении context перерисовываются подписчики.
<UserContext.Provider value={users}>
Большие объёмы данных приводят к:
TanStack Query использует более гранулярные подписки.
Компонент обновляется только при изменении конкретной query.
Context API не содержит встроенной модели async state.
Приходится вручную реализовывать:
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
TanStack Query предоставляет это автоматически.
MobX использует реактивную модель состояния.
class UserStore {
users = [];
constructor() {
makeAutoObservable(this);
}
}
Изменения автоматически отслеживаются.
MobX хорошо подходит для:
MobX не предоставляет готовую модель server state management.
Обычно приходится самостоятельно реализовывать:
В MobX часто возникает ситуация:
class Store {
users = [];
modalOpen = false;
loading = false;
}
Клиентское и серверное состояние смешиваются в одном store.
TanStack Query разделяет эти концепции архитектурно.
TanStack Query автоматически обновляет данные:
MobX требует ручной реализации подобных механизмов.
Zustand — лёгкое state management решение.
const useStore = create((set) => ({
bears: 0,
increase: () => set((state) => ({
bears: state.bears + 1
}))
}));
Zustand удобен для:
Для серверных запросов Zustand обычно требует ручной реализации:
const useStore = create((set) => ({
users: [],
loading: false,
fetchUsers: async () => {
set({ loading: true });
const response = await fetch('/api/users');
const users = await response.json();
set({
users,
loading: false
});
}
}));
Постепенно приходится самостоятельно добавлять:
В результате store усложняется.
Это один из наиболее популярных современных подходов.
Используется для:
Используется для:
SWR и TanStack Query очень близки идеологически.
Обе библиотеки:
const { data, error, isLoading } = useSWR(
'/api/users',
fetcher
);
SWR часто воспринимается как более минималистичное решение.
Особенно для:
TanStack Query предоставляет существенно больше возможностей.
useInfiniteQuery()
useMutation({
onMutate,
onError,
onSuccess
});
cacheTime
staleTime
gcTime
Полноценные инструменты анализа query-кэша.
queryClient.invalidateQueries()
enabled: !!userId
SWR проще для изучения.
TanStack Query мощнее, но имеет:
Apollo Client ориентирован на GraphQL.
Основные возможности:
Поддерживаются:
Apollo автоматически связывает сущности:
{
user {
id
name
}
}
Изменение entity обновляет связанные запросы.
TanStack Query использует query-based cache.
Кэш строится вокруг queryKey:
['users', userId]
TanStack Query менее навязчив архитектурно.
Нет необходимости:
Apollo особенно полезен при:
Recoil использует атомарное состояние.
const userState = atom({
key: 'userState',
default: null
});
const usersQuery = selector({
key: 'usersQuery',
get: async () => {
const response = await fetch('/api/users');
return response.json();
}
});
Recoil ориентирован на:
TanStack Query специализируется именно на async server state.
В Recoil обычно требуется ручное управление зависимостями.
TanStack Query предоставляет готовую модель:
invalidateQueries()
Vuex исторически использовался как централизованный store для Vue-приложений.
Недостатки при работе с серверным состоянием:
Pinia существенно упростила архитектуру.
export const useStore = defineStore('users', {
state: () => ({
users: []
})
});
Даже с Pinia приходится самостоятельно реализовывать:
Для Vue существует адаптация TanStack Query:
useQuery()
Подход остаётся тем же:
Вместо императивного управления:
fetchData();
setLoading(true);
используется декларативное описание данных:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
});
TanStack Query самостоятельно управляет:
Библиотека строится вокруг идеи:
серверное состояние — отдельный тип данных.
Это одна из главных причин популярности TanStack Query.
Библиотека может быть избыточной для:
Современные приложения часто используют несколько инструментов одновременно.
Для:
Для:
Отвечает за:
Отвечает за:
Большинство state manager библиотек пытаются универсально хранить любые данные.
TanStack Query рассматривает серверное состояние как отдельную архитектурную проблему и предоставляет специализированные механизмы:
Именно эта специализация сделала библиотеку одним из стандартов современного frontend-разработки.