State Management
Таксономия состояния
Команда положила список заказов с сервера в глобальный Redux-стор. Теперь на каждом экране приходится вручную писать, когда этот список перезагружать, что делать при ошибке, как не показать устаревшие данные после возврата на вкладку. Через полгода половина кода стора это самописный кэш, который плохо повторяет то, что TanStack Query делает из коробки. Корень беды один: серверные данные засунули в категорию глобального клиентского состояния. Разные виды состояния требуют разных инструментов, и ошибка в категории дорого обходится.
- Список заказов и профиль с сервера: серверное состояние, ему нужен кэш и контроль свежести
- Открыта ли модалка, активная вкладка: локальное UI-состояние, живёт в одном компоненте
- Выбранная тема и текущий пользователь: глобальное клиентское состояние на всё приложение
- Фильтры каталога в адресной строке: URL-состояние, чтобы ссылку можно было расшарить
- Поля и валидация большой формы: форменное состояние со своими библиотеками
- Шаги визарда или плеер (idle, loading, playing, error): машинное состояние через конечный автомат
Предварительные знания
- Понимание проблемы синхронизации и рассинхрона из предыдущего урока
- Идея локального состояния компонента на уровне знакомства
- Общее представление о том, что часть данных приходит с сервера по сети
Семь видов состояния
Слово состояние объединяет очень разные вещи. Полезно различать как минимум семь категорий, потому что у каждой свой жизненный цикл, своя область видимости и свой подходящий инструмент. Категория определяется не размером данных, а тем, кто владелец данных и как они меняются.
| Вид состояния | Пример | Где живёт / чем держать |
|---|---|---|
| Локальное UI | Открыт ли дропдаун, текст в одном поле | В компоненте (useState) |
| Общее | Состояние, нужное паре соседних компонентов | Поднятое состояние или контекст |
| Глобальное клиентское | Тема, текущий пользователь, открытые панели | Стор (Zustand, Redux, Jotai) |
| Серверное | Список заказов, профиль, цены | Кэш запросов (TanStack Query, SWR) |
| URL | Фильтры, номер страницы, поисковый запрос | Адресная строка (роутер, nuqs) |
| Форменное | Поля, ошибки, признак отправки формы | Библиотека форм (React Hook Form) |
| Машинное | Шаги визарда, статусы плеера | Конечный автомат (XState) |
Эти категории не конкурируют, а сосуществуют. В одном экране может быть локальное состояние дропдауна, серверный список, URL-фильтры и глобальная тема одновременно. Задача не выбрать одну категорию на всё приложение, а узнавать категорию каждого куска данных в отдельности.
Канонический стек 2026 года прямо отражает эту карту: Zustand для клиентского состояния, TanStack Query для серверного, nuqs для URL. Это не три конкурента, а три инструмента под три разные категории.
Что в первую очередь определяет категорию куска состояния?
Почему серверное состояние особенное
Серверное состояние выделяется тем, что приложение не владелец этих данных. Истинный список заказов живёт в базе на сервере, а то, что в памяти браузера, это лишь снимок, который мгновенно может устареть: другой пользователь добавил заказ, цена изменилась, статус обновился. Поэтому серверному состоянию нужны вещи, которых нет у клиентского: кэш, понятие свежести, повторные запросы при фокусе вкладки, дедупликация, обработка загрузки и ошибки.
- Клиентское состояние — Приложение владелец. Открытая модалка существует только здесь, никто снаружи её не меняет, синхронизировать не с кем. Значение просто хранится.
- Серверное состояние — Владелец - сервер. Данные в браузере это копия, которая может разойтись с источником в любой момент. Нужны свежесть, перезапросы и кэш, а не просто хранение.
Признак того, что серверные данные попали не в ту категорию: в глобальном сторе появляются поля вроде ordersLoading, ordersError и ручные перезагрузки. Это означает, что приложение начало писать собственный кэш вместо того, чтобы взять готовый инструмент серверного состояния.
Чем серверное состояние принципиально отличается от клиентского?
Ошибка номер один: неверная категория
Самая частая и дорогая ошибка в state management - отнести данные не к той категории. Классический случай: серверные данные кладут в глобальный клиентский стор. Дальше приходится вручную делать то, что у инструмента серверного состояния есть из коробки: загрузку, ошибку, инвалидацию, перезапрос. Стор разбухает самописным кэшем, и баги со свежестью возвращаются.
- Сначала вопрос: кто владелец этих данных - приложение или сервер?
- Если сервер - это серверное состояние, его держит кэш запросов, а не глобальный стор
- Если приложение, и данные нужны многим - глобальное клиентское состояние
- Если данные осмысленно расшаривать ссылкой - это URL-состояние
- Если данные нужны только одному компоненту - локальное состояние, без всякого стора
| Что хранят | Частая ошибка | Верная категория |
|---|---|---|
| Список товаров с сервера | Глобальный стор + ручной перезапрос | Серверное (кэш запросов) |
| Активный фильтр каталога | Локальный useState, теряется при перезагрузке | URL-состояние |
| Открыт ли тултип | Глобальный стор на всё приложение | Локальное UI-состояние |
| Текущая тема оформления | Проброс пропсами через всё дерево | Глобальное клиентское |
Полезная привычка: перед выбором библиотеки назвать категорию вслух. Это серверное? Тогда кэш запросов. Это нужно в ссылке? Тогда URL. Это только локальное? Тогда useState. Имя категории почти всегда подсказывает инструмент само.
Команде нужно показывать список заказов с сервера на нескольких экранах. Куда его поместить?
Связь с другими темами
Эта классификация - карта, по которой выбираются инструменты в остальном курсе:
- Единый источник истины — Правильная категория помогает определить, где находится каноническое место для каждого куска данных
- TanStack Query — Инструмент категории серверного состояния: кэш, повторные запросы, свежесть
- Zustand — Инструмент категории глобального клиентского состояния без лишней обвязки
Итог
- Состояние делится на категории: локальное UI, общее, глобальное клиентское, серверное, URL, форменное, машинное
- Серверное состояние принципиально отличается: данные живут в базе, а в приложении это лишь кэш, которому нужна свежесть
- URL-состояние (фильтры, страница, поиск) хранится в адресной строке, чтобы быть расшариваемым и переживать перезагрузку
- Каждой категории соответствует свой класс инструментов, и универсального одного хранилища на всё не существует
- Ошибка номер один - неверная категория, чаще всего серверные данные, засунутые в глобальный клиентский стор
- Распознать категорию стоит до выбора библиотеки: сначала какой это вид состояния, потом чем его держать
Связанные уроки
- sm-01-why-state-management — Сначала нужно понять саму проблему синхронизации, чтобы увидеть, зачем делить состояние на категории
- rc-36-tanstack-query — TanStack Query это инструмент именно для серверного состояния - одной из категорий этого урока
- rc-38-zustand-state — Zustand закрывает категорию глобального клиентского состояния