State Management
Как выбрать инструмент
Новый разработчик в команде заводит для списка товаров с сервера глобальный Redux-store, кладёт туда же выбранную вкладку из URL и флаг открытой модалки. Через месяц список разъезжается с сервером, кнопка назад в браузере не работает, а половина действий это ручная синхронизация кэша. Проблема не в Redux. Проблема в том, что четыре разных вида состояния свалены в один инструмент. Правильный выбор начинается с вопроса не как хранить, а что это за состояние.
- Список товаров, профиль, цены с сервера - серверное состояние под TanStack Query
- Активная вкладка, фильтры, строка поиска в URL - состояние URL под nuqs
- Открытые панели, выбранный инструмент, черновик формы - клиентское состояние под Zustand
- Мастер оформления заказа со сложными переходами шагов - конечный автомат под XState
- Команды, разнёсшие свалку из одного store по подходящим инструментам и убравшие ручную синхронизацию
Предварительные знания
- Различие серверного и клиентского состояния и почему его нельзя смешивать
- Понимание, что данные с сервера это кэш, а не источник истины на клиенте
- Идея кэша, свежести и инвалидации для серверных данных
Дерево решений
Прежде чем выбирать библиотеку, состояние нужно классифицировать. Четыре вопроса по порядку отсекают неподходящие инструменты. Первый: приходит ли значение с сервера? Если да, это серверное состояние, и его место в кэше запросов, а не в клиентском store. Второй: должно ли значение жить в URL? Фильтры, вкладки и строка поиска просятся в адресную строку, чтобы работали ссылки и кнопка назад.
Третий вопрос: есть ли сложные переходы с правилами, какие состояния допустимы и из какого в какое можно перейти? Это область конечных автоматов вроде XState. Четвёртый: значение действительно глобально и нужно несвязанным частям приложения? Тогда общий клиентский store, например Zustand. Если ни один вопрос не дал да - значение локально, и достаточно обычного useState рядом с компонентом.
Порядок вопросов важен. Серверное состояние идёт первым, потому что попытка хранить серверные данные в клиентском store порождает больше всего ошибок: ручную синхронизацию, рассинхрон с сервером и самописный кэш.
Список товаров приходит с сервера и показывается на нескольких страницах. Куда его положить по дереву решений?
Дефолтный стек 2026 года
Дерево решений на практике складывается в устоявшийся стек. К 2026 году дефолтный набор для типичного клиентского приложения такой: Zustand для клиентского состояния, TanStack Query для серверного, nuqs для состояния в URL. Каждый инструмент закрывает свой вид состояния и не лезет в чужой. Это не единственно возможный набор, а разумная отправная точка, требующая минимума обоснований.
| Вид состояния | Инструмент | Примеры |
|---|---|---|
| Серверное | TanStack Query | Список с API, профиль, цены, статус загрузки |
| Состояние URL | nuqs | Фильтры, активная вкладка, строка поиска, страница пагинации |
| Клиентское глобальное | Zustand | Открытые панели, выбранный инструмент, тема, черновик |
| Сложные переходы | XState | Мастер заказа, плеер, поток авторизации |
| Локальное | useState | Открыт ли один аккордеон, значение одного инпута |
nuqs синхронизирует состояние с query-параметрами URL: смена сортировки меняет адрес, ссылку можно отправить коллеге, а кнопка назад возвращает прежний фильтр. Это снимает с клиентского store то, что по природе принадлежит URL. Связка трёх инструментов покрывает почти весь стейт типичного приложения без дублирования ответственности.
Серверные компоненты React сокращают потребность в глобальном клиентском store: данные, отрисованные на сервере, не нужно держать в клиентском состоянии. Это смещает дефолтный стек ещё дальше в сторону серверного состояния и URL, оставляя Zustand чисто клиентским мелочам.
Какой набор образует дефолтный стек состояния 2026 года для типичного клиентского приложения?
Распространённые антипаттерны
Большинство проблем со стейтом это не выбор плохой библиотеки, а помещение состояния не в тот вид инструмента. Самый частый антипаттерн - копия серверных данных в клиентском store, которую приходится вручную обновлять. Это превращает Zustand или Redux в самописный кэш без свежести и инвалидации, ровно ту боль, ради которой создан TanStack Query.
- Серверные данные в клиентском store с ручной синхронизацией вместо кэша Query
- Глобализация локального значения: один флаг открытого аккордеона переезжает в глобальный store без нужды
- Фильтры и вкладки в клиентском store вместо URL, отчего ломаются ссылки и кнопка назад
- Дублирование: одно и то же значение лежит и в Query, и в Zustand, и они расходятся
- Конечный автомат, собранный из россыпи булевых флагов вместо явных состояний и переходов
Признак антипаттерна с серверными данными: в клиентском store есть поле, которое после каждого запроса вручную перезаписывается ответом сервера. Это сигнал перенести его в TanStack Query, где кэш, фоновое обновление и инвалидация уже встроены.
Обратный антипаттерн - преждевременная глобализация. Значение, нужное одному компоненту, не стоит тащить в глобальный store на всякий случай. Это раздувает общий стейт и усложняет рассуждение о том, кто и когда его меняет. Правило: начинать с локального useState и поднимать состояние выше только когда появился реальный второй потребитель.
Лечение свалки в одном store
Каждый вид состояния попал в подходящий инструмент, и ручная синхронизация исчезла
Был один Redux-store с массивом товаров, активной вкладкой и флагом модалки. Массив переехал в TanStack Query (исчезла ручная синхронизация и появилось фоновое обновление), вкладка в nuqs (заработали ссылки и кнопка назад), флаг модалки остался локальным useState. Глобальным остался лишь выбранный инструмент редактора в Zustand.
В клиентском store есть поле products, которое после каждого ответа API вручную перезаписывается. Что это за сигнал?
Связь с другими темами
Этот урок про выбор. Конкретные инструменты разобраны отдельно:
- Серверное и клиентское состояние — Первая развилка дерева решений: данные с сервера это кэш, а не клиентское состояние
- Zustand — Клиентская часть дефолтного стека: точечные ре-рендеры через селекторы без Provider
- TanStack Query — Серверная часть дефолтного стека: кэш, свежесть и инвалидация серверных данных
Итог
- Выбор инструмента начинается с вопроса не как хранить, а каков вид состояния
- Дерево решений: данные с сервера, состояние URL, сложные переходы, действительно глобальное клиентское значение
- Серверное состояние держит TanStack Query, не клиентский store: это кэш с свежестью и инвалидацией
- Состояние URL (фильтры, вкладки, поиск) кладут в адресную строку через nuqs ради ссылок и кнопки назад
- Сложные переходы с правилами и невозможными состояниями описывает конечный автомат, например XState
- Дефолтный стек 2026 года: Zustand для клиентского, TanStack Query для серверного, nuqs для URL
Связанные уроки
- sm-30-server-vs-client-state — Разделение серверного и клиентского состояния это первая развилка дерева решений и фундамент всего выбора
- rc-38-zustand-state — Zustand это клиентская часть дефолтного стека 2026 года, разобранная на реальном коде React
- rc-36-tanstack-query — TanStack Query закрывает серверное состояние в дефолтном стеке, его кэш и свежесть разобраны отдельно