Чому Context не завжди підходить для глобального стану?
Ключова ідея
React Context не дорівнює глобальному стору, він задуманий як механізм передачі даних вниз по дереву без prop drilling, а не як повноцінна система керування станом.
Чому Context не завжди підходить для глобального state
1. Кожна зміна value викликає ререндер усіх споживачів
Коли ти пишеш:
<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":
<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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.