Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Глобальний стейт і продуктивність». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Глобальний стан** - це дані, до яких можуть звертатися **різні компоненти з різних частин застосунку** (поточний користувач, тема, авторизація, кошик тощо), і які зазвичай зберігаються в Context API, Redux, Zustand, Jotai чи MobX. **Ключове:** зберігай мінімально необхідне в глобальному стані, все інше тримай локально, якомога ближче до місця використання - бо кожне оновлення глобального стану викликає масові перерендери всіх підписників, навіть якщо вони не використовують змінене поле.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Що таке "глобальний стан" **Глобальний стан** - це дані, до яких можуть звертатися **різні компоненти з різних частин застосунку**, наприклад: - поточний користувач, - тема (dark/light), - авторизація, - кошик товарів, - налаштування застосунку тощо. Такі дані зазвичай зберігаються в: - React Context API (`useContext`); - Redux / Zustand / Jotai / MobX; - глобальних store-синглтонах. --- ## Проблема: **надлишковий глобальний стан** Коли розробник починає класти **все підряд** у глобальний store, навіть те, що потрібно **лише одному компоненту**, виникає **ланцюгова реакція перерендерів і ускладнення** застосунку. --- ## Чому це шкідливо для продуктивності ### 1. **Кожне оновлення глобального стану викликає масові перерендери** React за замовчуванням **сповіщає всіх споживачів контексту (або store)**, які його читають, навіть якщо вони **не використовують змінене поле**. ```javascript const ThemeContext = createContext(); function App() { const [theme, setTheme] = useState('light'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Header /> <Main /> <Footer /> </ThemeContext.Provider> ); } ``` Якщо ти зміниш `theme`, → **всі компоненти, що використовують** `useContext(ThemeContext)`, будуть **перерендерені**, навіть якщо вони не відображають тему напряму. Чим більше компонентів прив'язано до контексту, тим більше непотрібних оновлень. --- ### 2. **Втрачається "локалізація" стану** Якщо все зберігається в одному великому store, будь-який апдейт → **тригерить підписників по всьому дереву**. Наприклад, зміна "пошукового запиту" в списку товарів не повинна чіпати "панель профілю користувача", але якщо все в одному Redux-слайсі, обидва перерахуються. --- ### 3. **Неможливо ефективно мемоізувати** Коли дані глобальні, складно ізолювати компонент: ```javascript const user = useSelector((state) => state.user); ``` → При будь-якій зміні в `state.user` React-Redux тригерить перерендер, навіть якщо компонент використовує лише `user.name`. Якщо таких "дрібних" підписок десятки, починається "каскад перерендерів". --- ### 4. **Контексти не вміють часткових оновлень** React Context **не підтримує дифф за значеннями**. Якщо у value хоч щось змінилося, всі `useContext`-споживачі отримують новий об'єкт → **перерендер**. ```javascript <Provider value={{ theme, user }}>...</Provider> ``` → навіть якщо змінився `user`, компонент, що використовує лише `theme`, все одно перемалюється, тому що об'єкт `{ theme, user }` новий за посиланням. --- ### 5. **Зростає когнітивне навантаження** Чим більше глобального стану, тим важче зрозуміти: - що де зберігається; - хто оновлює; - які компоненти залежать від чого. Це призводить до **непередбачуваних побічних ефектів**, і оптимізація стає складнішою, ніж сама бізнес-логіка. --- ### 6. **Проблеми з конкурентним рендерингом** У React 18 при увімкненому concurrent mode React може: - призупинити рендер; - відкотити зміни; - перерендерити частину дерева. Великий глобальний store робить це **дорожче**, тому що React змушений **копіювати і тримати знімки** більшого обсягу даних. --- ## Приклад (наочно) ```javascript // все в глобальному контексті - погана практика const GlobalContext = createContext(); function App() { const [theme, setTheme] = useState('light'); const [cart, setCart] = useState([]); const [search, setSearch] = useState(''); const value = { theme, cart, search, setTheme, setCart, setSearch }; return ( <GlobalContext.Provider value={value}> <Header /> // використовує лише theme <Search /> // використовує лише search <Cart /> // використовує лише cart </GlobalContext.Provider> ); } ``` Якщо змінити `search`, - **Header і Cart все одно перерендеряться**, тому що `value` (об'єкт) змінився за посиланням. --- ## Рішення (як зробити правильно) **1. Розділяй контексти / стора** Створюй **окремі провайдери** для незалежних частин: ```javascript <ThemeProvider> <CartProvider> <SearchProvider> <App /> </SearchProvider> </CartProvider> </ThemeProvider> ``` Кожен контекст оновлюється ізольовано. --- **2. Тримай локальний стан локальним** Якщо дані потрібні лише одному компоненту (або його дітям), використовуй `useState` прямо в ньому, а не в Redux/Context. ```javascript function SearchBar() { const [query, setQuery] = useState(''); ... } ``` --- **3. Роби селективні підписки** Якщо використовуєш Redux / Zustand, використовуй **селектори** і **shallow порівняння**, щоб підписуватися **лише на потрібні поля**. ```javascript const price = useSelector(state => state.cart.totalPrice); ``` → Зміниться лише якщо `totalPrice` зміниться. --- **4. Використовуй мемоізацію і split-компоненти** - Обгортай важкі компоненти в `React.memo`; - Передавай обробники через `useCallback`; - Дроби компонент так, щоб глобальні дані не тягли все дерево. --- **5. Для складних станів - useContextSelector (або Jotai / Zustand)** React 19 додасть **Context Selectors** нативно. Поки можна використовувати `use-context-selector`: вона дозволяє підписуватися **на конкретне значення** в контексті, а не на весь об'єкт. --- ## Підсумок | Причина | Чому шкідливо | |---|---| | Масові перерендери | Усі підписники оновлюються навіть при дрібній зміні | | Погана ізоляція | Будь-яка зміна тригерить все дерево | | Важкі знімки | Збільшує пам'ять і роботу GC | | Складно зрозуміти залежності | Зростає ймовірність багів | | Неможлива точкова оптимізація | Context не робить дифф за полями | --- ## Правильне мислення: > "Зберігай **мінімально необхідне** в глобальному стані. > Все інше тримай **локально**, якомога ближче до місця використання."Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.