Управление состоянием в React: Context, Redux или Zustand
Практика3 минРедакция Innovialabs
Коротко
Сначала отделите серверные данные (их отдайте TanStack Query) от клиентского состояния; для редких глобальных значений хватит Context, для частых — Zustand, для большой команды со строгими правилами — Redux Toolkit.
Короткий ответ
Большинство проблем с состоянием в React возникает из-за того, что разные виды состояния хранят одним способом. Правильный выбор начинается не с библиотеки, а с вопроса: что это за данные?
- Серверные данные (списки, профили, заказы) — TanStack Query или аналог.
- Локальное состояние (открыт ли модал, значение поля) —
useStateв компоненте. - Глобальное клиентское состояние (тема, корзина, авторизованный пользователь) — Context, Zustand или Redux.
Три вида состояния
Локальное. Нужно одному компоненту или небольшому поддереву. Храните его как можно ближе к месту использования. Поднимайте выше только когда данные реально нужны соседям.
Глобальное клиентское. Нужно многим частям приложения и существует только в браузере: тема, язык, содержимое корзины до оформления, состояние сложного мастера.
Серверное. Копия данных, которые живут на сервере. У него свои задачи: кеширование, повторная загрузка, устаревание, оптимистичные обновления, пагинация. Хранить такие данные в Redux вручную — частый источник лишнего кода.
Сравнение инструментов
| Инструмент | Для чего подходит | Минусы |
|---|---|---|
| Context | Редко меняющиеся значения: тема, язык, текущий пользователь | При изменении значения перерисовываются все потребители |
| Zustand | Глобальное состояние с частыми обновлениями, минимум кода | Меньше встроенных соглашений, структуру задаёт команда |
| Redux Toolkit | Большие приложения, сложная бизнес-логика, много разработчиков | Больше шаблонного кода и понятий для изучения |
| TanStack Query | Серверные данные: загрузка, кеш, мутации | Не предназначен для чисто клиентского состояния |
Context: встроенный, но не менеджер состояния
Context — это механизм передачи значения вниз по дереву без пропсов. Сам по себе он не оптимизирует обновления: если значение провайдера меняется, перерисовываются все компоненты, которые его читают.
Практические правила:
- Разделяйте контексты по смыслу:
ThemeContextотдельно отCartContext. - Мемоизируйте значение провайдера, если передаёте объект.
- Не кладите в Context то, что меняется при каждом вводе символа.
Zustand: простое глобальное хранилище
import { create } from 'zustand';
export const useCart = create((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
clear: () => set({ items: [] }),
}));
// В компоненте подписываемся только на нужную часть
const count = useCart((s) => s.items.length);
Компонент перерисуется только при изменении выбранного селектором значения. Провайдер не нужен, кода мало — поэтому Zustand часто выбирают для небольших и средних проектов.
Redux Toolkit: когда нужна строгость
Redux Toolkit — современный способ писать на Redux. Он даёт предсказуемый однонаправленный поток данных, удобные DevTools с историей действий и устоявшиеся паттерны. Это ценно, когда над кодом работает много людей и бизнес-логика сложная. В комплекте есть RTK Query для серверных данных.
TanStack Query: серверное состояние отдельно
const { data, isPending, error } = useQuery({
queryKey: ['orders', userId],
queryFn: () => fetchOrders(userId),
});
Библиотека сама кеширует ответы, дедуплицирует запросы и обновляет данные при возвращении на вкладку. После переноса серверных данных сюда глобального клиентского состояния обычно остаётся совсем немного.
Как выбрать по размеру приложения
- Лендинг или небольшое приложение:
useState+ Context для темы и языка. - Среднее приложение с API: TanStack Query для данных + Zustand или Context для остального.
- Крупный продукт, большая команда: TanStack Query или RTK Query + Redux Toolkit для сложной клиентской логики.
Частые ошибки: хранить всё глобально «на всякий случай», дублировать серверные данные в нескольких хранилищах, выбирать Redux для приложения из пяти экранов.