Skip to main content

Як оптимізувати роботу з контекстом?

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 сервісів тощо.

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

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

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