Как я выбираю, где хранить состояние в React-приложении
В React можно запихнуть всё в один глобальный стор и жить в Redux. А можно утонуть в пропсах и коллбэках. Я стараюсь выбирать инструмент под задачу: от локального useState до Zustand/Redux, но с понятными границами.
Расскажу, как я обычно раскладываю состояние в проектах.
1. Сначала думаю про «радиус» состояния
Я задаю один простой вопрос: кому это состояние нужно?
-
Нужно только одному компоненту Беру useState/useReducer локально.
-
Нужно дереву внутри страницы Думаю про контекст или композицию через props.
-
Нужно по всему приложению Тогда уже смотрю в Zustand/Redux/MobX и т.п.
Ошибка, которую вижу часто: глобальный стор заводят «на всякий случай» и тащат туда даже состояние модалки.
2. Локальное состояние: базовый кирпич
Для форм, модалок, вкладок мне почти всегда хватает:
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(prev => !prev);
Если логика сложнее (много полей, несколько действий), использую useReducer:
type State = { step: number; data: FormData }; type Action = { type: 'NEXT' } | { type: 'SET_DATA'; payload: FormData };
function reducer(state: State, action: Action): State { switch (action.type) { case 'NEXT': return { ...state, step: state.step + 1 }; case 'SET_DATA': return { ...state, data: action.payload }; } }
Локальный стейт проще тестировать и не требует внешних зависимостей.
3. Контекст: только для «сквозных» вещей
Я использую React.createContext для:
- текущего пользователя
- темы/настроек интерфейса
- текущего языка, токенов фич-флагов
Важно не превращать один контекст в «свалку всего», иначе любое изменение перерендерит полприложения.
4. Глобальный стор: когда без него уже больно
Я подключаю Zustand/Redux, когда:
- много сущностей (users, orders, products)
- данные переиспользуются на разных страницах
- нужны кэширование запросов, нормальные эффекты, девтулы
Главное правило: в сторе храним данные домена, а не локальное состояние UI вроде «открыта ли эта конкретная модалка».
5. React Query / SWR: стейт запросов, а не бизнес-логики
Отдельная категория — загрузка данных. Для этого я всё чаще использую React Query:
- кеширует ответы
- даёт статус загрузки/ошибок
- поддерживает рефетч, инвалидацию
Тогда глобальный стор можно упростить или вообще обойтись без него, если проект не слишком сложный.
6. Как я раскладываю состояние по слоям
Обычно придерживаюсь такой схемы:
-
локальный стейт для UI-мелочей (открытие модалки, активная вкладка, значения формы до сабмита)
-
контекст для сквозных вещей (тема, язык, авторизация)
-
React Query для данных, которые приходят из API
-
Zustand/Redux только когда есть сложная доменная логика, кэш, вычисляемые селекторы
Так приложение получается читаемым: открываешь файл и понимаешь, каких уровней состояния он касается.
Чек-лист перед тем, как заводить глобальный стор
- Понимаю, какие данные реально нужны на нескольких страницах
- Попробовал решить задачу локальным стейтом и/или контекстом
- Для данных с API уже использую React Query / SWR, не дублирую кэш руками
- Есть чёткое разделение: доменные данные в сторе, UI-мелочи в компонентах
- Понимаю, кто будет поддерживать эту архитектуру в команде
Итог
Управление состоянием в React — это не выбор «Redux или нет», а расстановка границ: что живёт в компоненте, что в контексте, а что в отдельном сторе. Если не тащить всё в глобальный стейт и использовать специальные инструменты для запросов, проект остаётся предсказуемым, а добавление новых фич не превращается в борьбу с бесконечными пропсами и перерендерами.