Коли Zustand у React перестає справлятися із завданням
Zustand хороший рівно до того моменту, поки стан залишається плоским і передбачуваним. Як тільки в сторі з'являються вкладені сутності, похідні значення та асинхронні оновлення з кількох джерел, починають проявлятися симптоми, які складно списати на «поганий компонент».
Перша ознака — зайві перерендери. Селектор, що повертає новий об'єкт або масив на кожному виклику, ламає порівняння за посиланням, і підписані компоненти перерендерюються на будь-яку зміну стору, навіть не пов'язану з ними.
Профайлер показує десятки рендерів там, де очікувався один, а причина — не в React, а у формі селектора.
Друга — гонки під час оновлення стану. Дві асинхронні дії пишуть в один ключ, порядок відповідей не гарантований, і в інтерфейсі осідає застарілий результат.
Без явного керування порядком оновлень і скасування застарілих запитів таке відтворюється нестабільно й майже не ловиться під час налагодження.
Третя — неявні залежності між модулями. Один екран читає поле, яке пише інший, зв'язок ніде не оголошений, і видалення «безневинної» дії валить чужий сценарій. Стан перестає бути локальним, а межі відповідальності розмиваються.
Четверта — ускладнення налагодження стану. Коли логіка розмазана по діях, middleware і підписках, відновити ланцюжок «хто і коли змінив значення» без додаткового інструментування вже неможливо. Саме на цьому етапі команди зазвичай і замислюються, чи не час змінити підхід до state management.
Розбираємо, як це закривається: де достатньо дисципліни в селекторах і структурі стору, а де потрібен інший інструмент.
Що дає вибудуване управління станом
Передбачуваність стану замість «магії» в компонентах
Коли стан живе в десятках локальних хуків і розмазаний по дереву компонентів, будь-яка зміна перетворюється на археологію: незрозуміло, хто і коли змінив прапорець, чому перемалювався список і звідки взявся застарілий відповідь сервера.
Ми виносимо стан у єдине сховище з явними операціями — у кожної зміни є ім'я, місце і зрозумілий набір підписників. Інтерфейс починає поводитися однаково при повторних сценаріях, а не «іноді глючить».
Менше регресій інтерфейсу
З централізованим станом пов'язані екрани читають одні й ті самі дані, а не кожен свою копію. Правка в одному місці не ламає сусідній модуль: логіка оновлення описана один раз і перевикористовується.
На практиці це знижує кількість регресій інтерфейсу — ті сценарії, які раніше «відвалювалися» після доопрацювань, залишаються робочими, а перевірки покривають самі операції над станом, а не кожну кнопку окремо.
Час збірки та масштабування модулів
Розділення сховища на незалежні частини дозволяє збирати й підключати модулі окремо: новий розділ додається своєю областю стану, не зачіпаючи існуючі. Це скорочує час збірки — перезбирається тільки задіяний модуль, а не весь застосунок цілком.
При зростанні кількості екранів і команд структура залишається читабельною: видно, яка частина стану кому належить і де шукати джерело проблеми.
Що це дає замовнику
Доопрацювання перестають бути ризикованими: нова функціональність підключається передбачувано, а поведінка інтерфейсу не залежить від порядку кліків. Команда швидше локалізує дефекти, тому що стан і його зміни прозорі. Далі розберемо, як ми вибудовуємо таку структуру і що залишається у вас після роботи.
Підходи до організації стану: порівняння
Стан у React-застосунку майже ніколи не буває однорідним: частина даних живе всередині одного екрана, частина потрібна кільком гілкам дерева, а частина — всьому застосунку одразу.
Помилка на старті — тягнути все в один механізм: або роздувати локальні стани до глобальних, або складати в спільний стор те, що живе пару секунд. Нижче — чотири реальні варіанти, між якими обирають на практиці.
| Варіант | Коли підходить | Обмеження |
|---|---|---|
| Локальний стан | Дані потрібні одному компоненту або парі сусідів: форми, розкриті панелі, чернетки | При зростанні дерева з'являється «протягування» через проміжні компоненти |
| Контекст React | Значення, що рідко змінюються, для цілої гілки: тема, локаль, права доступу | Часте оновлення перемальовує всіх споживачів одразу |
| Зовнішній стор | Дані потрібні в різних частинах застосунку й змінюються часто: кошик, фільтри, сесії | Потребує дисципліни: без правил стор перетворюється на звалище |
| Комбінована схема | Великий застосунок, де співіснують усі три випадки | Потрібна явна межа: що і де зберігається, інакше логіка розповзається |
На практиці робоча конфігурація — комбінована: локальний стан закриває ізольовані сценарії, контекст віддає стабільні налаштування, зовнішній стор тримає розділювані дані.
Zustand у цій ролі зручний тим, що не вимагає обгортати застосунок у провайдери й дозволяє підписувати компонент на конкретний зріз — перемальовування не зачіпає решти.
Розбір варіантів потрібен, щоб вибрати не «модний», а відповідний під профіль навантаження. Далі показуємо, як ця схема розкладається на конкретному застосунку.
Як ми впроваджуємо Zustand у React-застосунок
Впровадження починаємо не з коду, а з інвентаризації: де зараз живе стан, що дублюється між компонентами, які оновлення спричиняють зайві перемальовування. Без цього кроку будь-яка бібліотека перетворюється на ще один шар поверх хаосу.
- Аудит стану. Проходимо по дереву компонентів і виписуємо, які дані спільні, які локальні, що тягнеться із серверного кешу. Окремо позначаємо місця, де стан передається через кілька рівнів або синхронізується вручну — саме вони дають найбільше помилок при зростанні застосунку.
- Проєктування схеми стору. Розбиваємо стан на незалежні зрізи за предметними областями, а не за екранами. Визначаємо, що залишається в локальному стані компонента, що переходить у спільний стор, а що — у кеш запитів. На цьому етапі фіксуємо правила іменування та структуру екшенів, щоб стор читався однаково всіма.
- Налаштування Zustand. Підключаємо стор, налаштовуємо селектори та підписки так, щоб компонент перемальовувався лише на своїх даних. Додаємо проміжні шари для налагодження та збереження частини стану між сесіями, де це виправдано.
- Перевірка сценаріїв. Проганяємо реальні користувацькі шляхи: авторизація, повернення на сторінку, конкурентні оновлення, відкат дій. Дивимося, що відбувається при швидких повторних кліках і при розсинхронізації із сервером.
- Передача документації. Віддаємо схему стору, правила додавання нових зрізів і чек-лист типових помилок. Команда замовника далі розширює застосунок без нас, не ламаючи архітектуру.
Кожен етап закінчується артефактом: картою стану, схемою зрізів, налаштованим стором, звітом за сценаріями та документом для розробників. Такий порядок прибирає головну причину проблем зі станом — рішення, ухвалені по ходу написання коду.
Артефакти, які залишаються в проєкті
Після впровадження zustand react у проєкті залишається не «магія в голові розробника», а набір файлів і правил, за якими команда живе далі. Нижче — що саме передаємо замовнику і навіщо кожен артефакт потрібен.
- Конфігурації стора — типізовані слайси за доменами (користувач, кошик, фільтри), розділення на серверний і клієнтський стан, middleware для логування та збереження в localStorage з версіонуванням ключів.
- Схеми даних — опис сутностей та їхніх зв'язків: які поля живуть у сторі, що приходить з бекенду, де проходить межа нормалізації. Схема відповідає на питання «чому це лежить тут, а не в компоненті».
- Політики доступу — правила, хто і коли може змінювати стан: селектори лише для читання, екшени з перевіркою ролі, заборона прямих мутацій поза стором. Окремо фіксуємо, які дані не потрапляють у стор взагалі.
- Документація — карта стора з призначенням кожного слайса, приклади підключення в компонентах, домовленості про іменування екшенів і селекторів. Новий розробник вводиться в контекст за години, а не за тижні.
- Регламенти — коли заводити новий слайс, як рев'юїти зміни стану, як відкочувати невдалий реліз стора. Це те, що утримує архітектуру від розповзання через пів року.
- Тести стану — покриття ключових сценаріїв: гідрація, скидання при виході користувача, гонки при паралельних запитах. Перевіряємо поведінку стора, а не внутрішню будову компонентів.
- Чек-ліст міграції — покроковий план переведення решти частин застосунку на новий стор, з точками відкату на кожному кроці.
Кожен артефакт існує в репозиторії поруч із кодом — не в листуванні і не в чужій голові. Опишіть задачу, і ми повернемося з оцінкою та планом: що увійде в передачу саме у вашому випадку.
Кейс: скорочення часу збірки та кількості регресій
Проєкт — особистий кабінет із десятками екранів на React: глобальний стан зростав стихійно, логіка була розмазана між контекстами та локальними сховищами компонентів. Кожна зміна моделі даних тягнула за собою правки в різних місцях, а тести падали хвилями.
Ми перевели керування станом на Zustand і розклали його за доменами — замість одного розростаючогося сховища з'явилися незалежні зрізи з чіткими межами.
Що було до
Збірка займала близько 14 хвилин: перезбирався весь граф модулів, бо один спільний контекст тягнув залежності в усі екрани. Регресії за станом — у середньому 6-8 на реліз: гонки при оновленні, розсинхрон між вкладками, втрачені значення після навігації.
Відновлення після інциденту розтягувалося на години — не було зрозумілого місця, де дивитися поточний стан та його історію.
Що змінили
- Розбили стан на доменні зрізи: авторизація, дані профілю, налаштування інтерфейсу — кожен оновлюється ізольовано.
- Винесли побічні ефекти з компонентів у підписки на зміни, прибрали дублюючі запити.
- Додали селектори з мемоізацією: компонент перемальовується тільки на своїх даних.
- Налаштували збереження частини стану між сесіями та скидання при виході.
Що стало
Збірка скоротилася до 5-6 хвилин — інкрементальні зміни перестали зачіпати весь граф. Кількість регресій за станом впала до 1-2 на реліз, а більшість ловиться на етапі тестів, не в проді.
Відновлення після збою займає хвилини: стан передбачуваний, його видно і можна відкотити точково. Витрати на підтримку перестали зростати — нові екрани підключаються до готових зрізів, а не тягнуть нову логіку.
Чим Zustand відрізняється від Redux і MobX?
Zustand — мінімалістичний стор для React: стан живе у звичайному об'єкті, компонент підписується на потрібний зріз через селектор і перемальовується лише при його зміні.
Провайдер і обгортка над деревом не потрібні, стор доступний і в компонентах, і поза React — в обробниках, утилітах, тестах. Поріг входу вимірюється десятками рядків: створити стор, описати екшени як звичайні функції, підключити в компоненті.
Redux вимагає більше церемоній: екшени, редюсери, диспетчеризація, а в типізованому варіанті — ще й робота з middleware для асинхронності.
Це виправдано там, де потрібні строгий журнал змін, відтворюваність і покрокове налагодження — наприклад, у застосунках із десятками сценаріїв і кількома командами. Але шаблонного коду помітно більше, і кожна нова фіча обростає файлами.
MobX іде іншим шляхом: стан — це спостережувані поля класу, а компоненти автоматично реагують на їхню зміну. Коду менше, ніж у Redux, але з'являється «магія» — не завжди очевидно, чому компонент перемалювався, а налагодження вимагає розуміння внутрішньої механіки реактивності.
За продуктивністю різниця проявляється на масштабі. Zustand із точковими селекторами перемальовує лише підписані компоненти, і на середньому застосунку це дає рівний відгук без ручної оптимізації.
Redux при неакуратних селекторах починає перемальовувати зайве, і доводиться підключати мемоізацію. MobX добре тримає часті оновлення, але ціна — складніше передбачити, що саме оновиться.
Налагодження — ключовий критерій вибору. У Redux є готові інструменти з часовою шкалою і відкатом, що безцінно при розборі складних сценаріїв.
У Zustand налагодження простіше за структурою: стор — це об'єкт, зміни видно у звичайному логері, але готової машини часу «з коробки» немає. MobX виграє у швидкості написання, програє у прозорості: чим більше реактивних зв'язків, тим дорожче розбирати несподівану поведінку.
На практиці вибір такий: Zustand — для більшості застосунків, де важливі швидкість розробки і передбачувані перемальовування; Redux — для великих систем із строгими вимогами до журналу і командної роботи; MobX — для інтерфейсів з інтенсивними оновленнями і невеликим ядром розробки.
Ми підбираємо стор під задачу, а не під моду — і закладаємо структуру так, щоб перехід між підходами залишався здійсненним.
Часті технічні сумніви
Zustand — не фреймворк, а невелика бібліотека поверх React: вона не диктує структуру проєкту і не вимагає обгортати застосунок у провайдери. Тому її однаково спокійно підключають і в свіжий проєкт, і в кодову базу, яка живе роками. Нижче — питання, які найчастіше задають перед стартом.
Чи сумісно це з нашим поточним React-застосунком?
Так. Zustand працює з будь-якою актуальною гілкою React, включно зі строгим режимом і серверним рендерингом, і не конфліктує з наявними провайдерами.
Стор можна підключити точково — на один екран або модуль — і поступово розширювати зону відповідальності, не переписуючи застосунок цілком.
Як проходить міграція з Redux або контексту?
Міграція йде частинами: спочатку переноситься одна незалежна ділянка стану, потім наступна. Логіка залишається читабельною, а старий і новий підходи якийсь час працюють паралельно — це дозволяє не зупиняти розробку і відкотитися на будь-якому кроці без втрати даних.
Чи не просяде продуктивність на великих обсягах даних?
Навпаки: компонент перемальовується тільки тоді, коли змінюється потрібний йому шматок стану, а не весь стор. На практиці це прибирає зайві перемальовування списків і таблиць, через які інтерфейс починає «підгальмовувати» при активній роботі користувача.
Що з підтримкою та розвитком рішення?
Ми залишаємо структуру сторів, правила іменування і схему потоків даних у документації проєкту. Будь-який інженер команди зможе додати новий стор або змінити наявний, не розбираючись у чужій логіці з нуля — підтримка не перетворюється на залежність від однієї людини.
Якщо ваш випадок не потрапив до списку — опишіть задачу, і ми повернемося з оцінкою та планом робіт.
Обговоримо ваше завдання з управління станом
Щоб повернутися з оцінкою та планом робіт, а не із загальними словами, нам потрібен первинний розбір вашого застосунку.
Опишіть завдання у вільній формі: де зараз зберігається стан, які екрани від нього залежать, що болить — зайві перемальовування, розсинхрон даних між вкладками, нечитабельні ланцюжки оновлень. Цього достатньо, щоб зрозуміти клас рішення.
Далі ми поставимо точкові запитання: як влаштована робота із серверними даними та кешем, чи є форми з довгим життєвим циклом, скільки розробників одночасно правлять одні й ті самі модулі, які сценарії вже покриті тестами. Якщо частина контексту закрита — не страшно, те, чого бракує, зберемо на дзвінку з вашим техлідом.
За підсумком первинного розбору ви отримаєте:
- карту поточного стану: які дані де живуть і хто ними володіє;
- перелік вузьких місць із зазначенням, що дає кожне виправлення;
- пропозицію щодо структури сховищ — де спільний стор, де локальний, де серверний кеш;
- план робіт за етапами з порядком впровадження та точками відкату;
- оцінку трудозатрат і складу команди під ваш обсяг;
- ризики міграції та спосіб перевести застосунок без зупинки релізів;
- критерії приймання: за якими метриками ви переконаєтеся, що стало краще.
План узгодимо до старту — ви бачите, що і в якому порядку змінюється, і можете скоригувати пріоритети. Опишіть завдання зручним способом: текстом, посиланням на репозиторій або коротким дзвінком. Повернемося з оцінкою та планом робіт.







