Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Оптимізація контексту». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Контекст перерендерює **всіх** підписників, коли змінюється посилання на `value` Provider'а, тому оптимізація зводиться до дроблення контексту на смислові частини, стабілізації `value` через `useMemo`/`useCallback` і переходу на точкові підписки (селектори) або зовнішній стор для частих оновлень. **Ключове:** рідкі оновлення й прості дані підходять для Context, часті й дрібнозернисті - краще винести у стор із селекторами (`useSyncExternalStore`, Zustand тощо).Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 1) Ділиш контекст на смислові частини Не клади все в один "товстий" `value`. - Було: `AppContext = { user, theme, notifications, cart }` - Стало: `AuthContext`, `ThemeContext`, `UIContext`, `CartContext` Так зміни в кошику не тригерять перемальовування підписників теми і т.д. --- ## 2) Стабілізуй `value` у Provider Ререндер усіх підписників спрацьовує при **зміні посилання**. Дай стабільні посилання: ```javascript function AuthProvider({ children }) { const [user, setUser] = useState<User | null>(null); // функції теж стабілізуємо const login = useCallback((u: User) => setUser(u), []); const logout = useCallback(() => setUser(null), []); // value змінюється тільки коли змінюється user const value = useMemo(() => ({ user, login, logout }), [user]); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } ``` **Уникай:** ```javascript <AuthContext.Provider value={{ user, login: (u)=>setUser(u) }}> ``` (кожен ререндер створює новий об'єкт/функції -> усі підписники оновляться). --- ## 3) Розділяй "стан" і "диспатч" на **два контексти** `dispatch`/екшени стабільно-посилальні, їх можна віддавати окремим провайдером: ```javascript const CounterStateContext = createContext<number>(0); const CounterDispatchContext = createContext<React.Dispatch<Action>>(() => {}); function CounterProvider({ children }) { const [count, dispatch] = useReducer(reducer, 0); return ( <CounterDispatchContext.Provider value={dispatch}> <CounterStateContext.Provider value={count}> {children} </CounterStateContext.Provider> </CounterDispatchContext.Provider> ); } // Підписник, якому потрібна лише відправка дій, не ререндериться при зміні count ``` --- ## 4) Став Provider якомога **нижче** в дереві Чим менше підписників усередині області дії Provider, тим менший масштаб оновлень. - Обгортай потрібні гілки, а не весь `<App />`. --- ## 5) Використовуй **селектори** (точкові підписки) Бібліотека `use-context-selector` дозволяє підписуватися **на частину value**, а не на весь об'єкт. ```javascript import { createContext, useContextSelector } from 'use-context-selector'; const UserContext = createContext<{user: User, setUser:Fn}>(/* ... */); function UserName() { const name = useContextSelector(UserContext, v => v.user.name); return <span>{name}</span>; // Ререндериться тільки якщо змінилося ім'я } ``` Це дає гранулярні оновлення як у Redux. --- ## 6) Обгортки-селектори + `React.memo` Іноді зручно зробити маленький компонент-селектор, який читає контекст і віддає **рівно потрібний проп** вниз мемоізованому компоненту. ```javascript const PriceView = React.memo(function PriceView({ price }: { price: number }) { return <span>{price}</span>; }); function PriceFromContext() { const price = useContext(CartContext).totalPrice; // тільки одне поле return <PriceView price={price} />; } ``` `React.memo` тут "відрізає" зайві ререндери нижче, якщо проп не змінився. --- ## 7) Не зберігай у контексті "товсті"/часто змінювані структури - Клади **ідентифікатори** і прості дані. - Важкі колекції/кеш тримай у зовнішньому сторі (`useSyncExternalStore`, Zustand/Jotai/Redux) або локально на місці. --- ## 8) Для частих оновлень - краще **зовнішній стор** Context не про high-frequency оновлення (таймери, курсор, введення). Для таких задач: - Zustand/Jotai/Redux + селектори - `useSyncExternalStore` із власною шиною підписок Вони оновлюють лише тих, хто реально читає частину, що змінилася. --- ## 9) Часті дрібниці, які рятують - Не передавай у `value` нові масиви/об'єкти без `useMemo`. - Екшени/колбеки - через `useCallback`. - У `Provider` не обчислюй важке на льоту - мемоізуй або виноси. - У TypeScript можна типізувати контексти окремо: `State` і `Actions` - менше спокуси "склеїти все в одне". --- ### Коротка "шпаргалка" вибору - **Рідкі оновлення + прості дані** -> Context (із п.1-6 вище). - **Багато різних полів, що часто змінюються** -> Розділити контексти або перейти на стор із селекторами. - **Дуже часті оновлення (введення, курсор, розміри вікна)** -> зовнішній стор/підписка (`useSyncExternalStore`, Zustand тощо). Якщо хочеш, накину міні-рефактор: надішли свій поточний Provider/підписників - перетворю на варіант із розділеними контекстами і селекторами.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.