Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як оптимізувати роботу з контекстом?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Оптимізація роботи з контекстом** зводиться до того, щоб посилання на `value` провайдера змінювалося лише тоді, коли справді змінюються його дані, і щоб рідко змінювані та часто змінювані частини стану не жили в одному контексті. **Ключове:** діліть контекст на дрібні за доменом, мемоізуйте `value` через `useMemo`/`useCallback`, розносьте "часто змінюється" і "рідко змінюється" по різних контекстах і тримайте Provider якомога ближче до споживачів.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 1) Діліть контекст на дрібні Не кладіть "все підряд" в один Provider. Розбийте за доменами - тоді змінюється одне значення -> ререндеряться тільки його споживачі. ```javascript <UserProvider value={user}> <ThemeProvider value={theme}> <I18nProvider value={i18n}> <App /> </I18nProvider> </ThemeProvider> </UserProvider> ``` ## 2) Мемоізуйте `value` Головне правило: **посилання** `value` **має змінюватися тільки коли змінюються його дані**. ```javascript function ThemeProvider({ children }) { const [theme, setTheme] = useState<'light'|'dark'>('light'); const value = useMemo(() => ({ theme, setTheme }), [theme]); // добре return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; } ``` Анти-приклад (кожен рендер створює новий об'єкт -> усі споживачі смикаються): ```javascript <ThemeContext.Provider value={{ theme, setTheme }}> {/* погано */} ``` ## 3) Розносьте "часто змінюється" і "рідко змінюється" Тримайте швидко змінюване окремо від стабільного. ```javascript const CounterValueContext = createContext<number>(0); const CounterActionsContext = createContext<{ inc: () => void }>({ inc: () => {} }); function CounterProvider({ children }) { const [count, setCount] = useState(0); const inc = useCallback(() => setCount(c => c + 1), []); return ( <CounterActionsContext.Provider value={{ inc }}> <CounterValueContext.Provider value={count}> {children} </CounterValueContext.Provider> </CounterActionsContext.Provider> ); } ``` Тепер зміна `count` **не** змінює посилання на `actions`, і споживачі дій не ререндеряться. ## 4) Тримайте Provider якомога ближче до споживачів Ставте Provider навколо тієї частини дерева, де реально потрібен контекст - менше "підписників" -> менше ререндерів. ## 5) Використовуйте селектори (точкове читання) З коробки React перемалює всіх consumers, але можна звузити оновлення бібліотеками контекст-селекторів: - `use-context-selector` - Zustand/Jotai/Redux Toolkit з `useSelector` Приклад з `use-context-selector`: ```javascript import { createContext, useContextSelector } from 'use-context-selector'; const CartContext = createContext<{items: Item[]; total: number}>({items:[], total:0}); function TotalPrice() { const total = useContextSelector(CartContext, v => v.total); // ререндер тільки при зміні total return <span>{total}</span>; } ``` ## 6) Не передавайте "свіжостворені" об'єкти/масиви Якщо потрібно віддавати похідні дані - мемоізуйте їх у споживача: ```javascript function ProductsList() { const { items } = useContext(ProductsContext); const visible = useMemo(() => items.filter(p => p.visible), [items]); return <List items={visible} />; } ``` ## 7) Для глобального змінюваного стану - `useReducer` `dispatch` стабільний за посиланням -> зручно передавати через контекст. ```javascript const StoreContext = createContext<{state: State; dispatch: React.Dispatch<Action>}>(null!); function StoreProvider({ children }) { const [state, dispatch] = useReducer(reducer, initial); const value = useMemo(() => ({ state, dispatch }), [state]); // dispatch стабільний return <StoreContext.Provider value={value}>{children}</StoreContext.Provider>; } ``` ## 8) Зовнішні стори: `useSyncExternalStore` Якщо потрібна **тонка підписка без ререндеру всього контексту**, винесіть дані у зовнішній стор і підпишіться: ```javascript const subscribe = (listener: () => void) => store.subscribe(listener); const getSnapshot = () => store.getState().pieceYouNeed; const usePiece = () => useSyncExternalStore(subscribe, getSnapshot); ``` Це дає "селективні" оновлення з коробки. ## 9) Стабілізуйте функції в `value` Якщо в `value` є функції - обгортайте їх у `useCallback`, щоб не змінювати посилання даремно. ```javascript const actions = useMemo(() => ({ save, remove }), [save, remove]); // де save/remove - useCallback ``` ## 10) Контекст - для "конфігу", не для "лічильників" Чим рідше змінюється контекст - тим краще. Часто оновлювані шматки стану тримайте локально (`useState` у компонентів) або у зовнішньому сторі; контекст - для конфігурації, тематики, прав, локалі, DI сервісів тощо.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.