Skip to main content

Чому Context не завжди підходить для глобального стану?

Ключова ідея

React Context не дорівнює глобальному стору, він задуманий як механізм передачі даних вниз по дереву без prop drilling, а не як повноцінна система керування станом.


Чому Context не завжди підходить для глобального state

1. Кожна зміна value викликає ререндер усіх споживачів

Коли ти пишеш:

javascript
<MyContext.Provider value={{ user, theme }}> <App /> </MyContext.Provider>

React порівнює value за посиланням. Якщо воно змінилося -> всі компоненти, що використовують useContext(MyContext), перемальовуються, навіть якщо потрібне їм поле не змінилося.

При великому дереві це дає каскадні оновлення і лаги.

Приклад: Змінилося user.name -> весь UI, що використовує theme, теж ререндериться.


2. Немає "селекторів" (гранулярних підписок)

Контекст не вміє підписувати компонент тільки на частину даних.

Якщо у value лежить { user, theme, cart }, то при зміні cart оновляться навіть ті, кому потрібен тільки theme.

Рішення - використовувати сторонні інструменти (use-context-selector, Zustand, Jotai), але "чистий" Context цього не дає.


3. Кожне оновлення проходить через React-рендер

Контекст не оптимізований під часто змінюваний стейт (наприклад, курсор, таймер, введення тексту). При 60 оновленнях за секунду React просто не встигає.

Context = UI-стан, а не "реактивний потік".


4. Немає інструментів, middleware і devtools

Context:

  • не має devtools;
  • не підтримує time-travel;
  • не дозволяє зручно логувати зміни, відкочувати state, дебажити бізнес-логіку;
  • не масштабується при складній архітектурі.

5. Важко масштабувати при рості застосунку

Коли застосунок росте, починаються "милиці": десятки контекстів (AuthContext, CartContext, UIContext, SettingsContext...), вкладені дерева Provider, заплутані залежності і "Provider hell":

javascript
<AuthProvider> <ThemeProvider> <UIProvider> <CartProvider> <App /> </CartProvider> </UIProvider> </ThemeProvider> </AuthProvider>

Важко підтримувати, тестувати і розширювати.


6. Контекст синхронний і локальний

Він не призначений для:

  • асинхронних запитів (fetch/cache/retry);
  • синхронізації з сервером;
  • офлайн-даних;
  • реактивних підписок (WebSocket, SSE тощо).

Для цього краще використовувати React Query, Zustand, Redux Toolkit та інші.


Коли Context все ж ідеальний

ЗастосуванняЧому підходить
Тема (light/dark)Просте, рідкісне змінення
Мова інтерфейсу (i18n)Оновлюється тільки при перемиканні мови
Поточний користувачЗмінюється тільки при логіні/логауті
Feature flags, налаштуванняМалий обсяг даних, низька частота змін
Compound componentsКонтекст використовується як внутрішня шина зв'язку (Tabs, Modal, Dropdown тощо)

Альтернатива для "реального" глобального state

ЗавданняІнструмент
Часто змінюваний стан, бізнес-логікаZustand, Jotai, Redux Toolkit
Асинхронні дані (fetch, кеш)TanStack Query (React Query)
Реактивні підписки, серверні пушіRxJS, useSyncExternalStore, власний стор
Прості статичні даніContext

Підсумок

ПричинаЧому Context не підходить
Часті оновленняВсі споживачі ререндеряться
Немає селекторівНе можна підписатися тільки на частину даних
Немає інструментівНе можна дебажити чи контролювати потік
Погана масштабованістьProvider hell і складні залежності
Немає асинхронностіВсе синхронно і локально

Висновок:

Context - це механізм "передачі даних вниз по дереву", а не "глобальне сховище стану".

Для невеликих глобальних прапорців - чудово. Для складного бізнес-стейту - краще Zustand, Jotai, Redux Toolkit або React Query.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.