Перейти к содержанию

Управление состоянием

Уровень: L2 (детали) | Вверх: ../README.md

Стратегия

Управление состоянием разделено на три уровня в зависимости от природы данных:

Уровень Инструмент Данные Где живёт
Клиентское Zustand Аутентификация, UI-состояние конструктора localStorage + RAM
Серверное TanStack Query Вещи, образы, справочники, лента HTTP-кэш + IndexedDB
Локальное useState/useRef Формы, модальные окна, фильтры Компонент

Обоснование выбора

Redux Toolkit не использован из-за избыточности: проект имеет только 2 стора с суммарно ~155 строками кода. Бойлерплейт Redux (slices, reducers, actions, Provider) неоправдан для такого объёма. MobX отклонён из-за зависимости от декораторов и большего размера бандла (~16 KB vs ~1 KB у Zustand). Zustand предоставляет минималистичный API: create() → стор готов, useStore(selector) → подписка с гранулярным ре-рендером.

Потоки данных

flowchart LR
    subgraph Client["Клиент"]
        UI["UI компоненты"]
        AuthStore["authStore\n(Zustand)"]
        BuilderStore["outfitBuilderStore\n(Zustand)"]
        QCache["TanStack Query\nкэш"]
        IndexedDB["IndexedDB\n(Dexie)"]
        LocalStorage["localStorage"]
    end

    subgraph Server["Сервер"]
        API["REST API\n(Kotlin)"]
    end

    UI -->|"setState"| AuthStore
    UI -->|"setSlotItem"| BuilderStore
    UI -->|"useQuery"| QCache

    AuthStore -->|"setTokens"| LocalStorage
    AuthStore -->|"hydrate"| LocalStorage

    QCache -->|"queryFn"| API
    QCache -->|"offline read"| IndexedDB

    API -->|"response"| QCache
    API -->|"initialSync"| IndexedDB

    IndexedDB -->|"useLocalItems"| QCache
    IndexedDB -->|"background refresh"| API

Диаграмма

Zustand-сторы

authStore

Хранит состояние аутентификации: JWT-токены, профиль пользователя, флаг isAuthenticated. Персистентность реализована через прямую запись в localStorage (не через middleware persist), что даёт контроль над процессом гидрации при запуске приложения.

Ключевые операции: - hydrate() — вызывается один раз в main.tsx, восстанавливает токены и пользователя из localStorage - setTokens(access, refresh?) — обновляет токены при логине и refresh - logout() — очищает все данные, включая IndexedDB (через clearLocalData())

outfitBuilderStore

Управляет состоянием конструктора образов: набор слотов (slots), выбранные вещи, заголовок, флаг выбора. Не использует persist — состояние существует только во время сессии конструктора.

Ключевые операции: - initSlots(slots) — инициализирует слоты из справочника - setSlotItem(slotId, item) — привязывает вещь к слоту - clearAll() — сбрасывает конструктор

TanStack Query — паттерны

Все серверные данные загружаются через TanStack Query с единой конфигурацией queryClient:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,  // 5 минут
      networkMode: 'always',       // работа в offline
    },
  },
});

Ключевые паттерны: - Offline-first хуки (useLocalItems, useLocalOutfits) читают из IndexedDB мгновенно, фоново обновляют с сервера - useInfiniteQuery для бесконечной ленты с курсорной пагинацией - Инвалидация кэша в onSuccess мутаций: queryClient.invalidateQueries(['outfits']) - networkMode: 'always' позволяет выполнять запросы к IndexedDB даже без сети

-> Полный код stores: zustand-stores.md | Паттерны Query: tanstack-query-patterns.md