Skip to main content

Ререндер дочірніх елементів

Коротко

Коли змінюється значення, передане в <Context.Provider value={...}>, React перемальовує всі компоненти, які використовують цей контекст через useContext() або <Context.Consumer>.


Чому це відбувається

Тому що Context - це глобальний механізм підписки.

React робить приблизно наступне:

  1. У кожного контексту є список споживачів (components subscribed).
  2. Коли Provider отримує нове значення value, React порівнює нове значення з попереднім (за посиланням).
  3. Якщо посилання змінилося (!==), він сигналізує всім підписникам, що "значення змінилося".
  4. Кожен компонент, що використовує useContext(), виконує повторний рендер, щоб отримати нове значення.

Головне: порівняння йде за посиланням, не за вмістом

javascript
<ThemeContext.Provider value={{ mode: "dark" }}> <App /> </ThemeContext.Provider>

Навіть якщо mode не змінився, при кожному рендері App створюється новий об'єкт ({ mode: "dark" } !== { mode: "dark" }), і React думає, що значення контексту змінилося -> всі підписники перемальовуються.


Як цього уникнути - мемоізація value

Потрібно передавати стабільне посилання за допомогою useMemo:

javascript
function ThemeProvider({ children }) { const [mode, setMode] = useState("light"); const value = useMemo(() => ({ mode, setMode }), [mode]); return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; }

Тепер об'єкт value створюється тільки при зміні mode, а не при кожному рендері - і контекст не викликає каскадний ререндер даремно.


Аналогія

Можна уявити, що Provider - це "маяк", який каже всім підписникам:

"Гей, значення змінилося, оновіться!"

React не перевіряє, що саме змінилося всередині об'єкта - він просто бачить, що "маяк подав новий сигнал" (нове посилання), і оновлює всіх підписників.


До чого призводить "необережне" використання контексту

ПроблемаПриклад
Масові ререндериОдин Provider оновлює 100+ компонентів, і всі ререндеряться
Зайві обчисленняКомпоненти отримують те саме значення, але все одно перераховуються
Втрата продуктивностіОсобливо при передачі "товстих" об'єктів у value

Способи оптимізації

  1. Розділяй контексти Не зберігай усе в одному великому контексті - створюй незалежні: AuthContext, ThemeContext, UIContext тощо.
  2. Використовуй useMemo для value
javascript
const value = useMemo(() => ({ user, setUser }), [user]);
  1. Ізолюй "важких" споживачів Якщо контекст часто оновлюється - обгортай частину UI в memo або виноси окремі Providers.
  2. Використовуй селектори (наприклад, з бібліотекою use-context-selector) Вона дозволяє компоненту підписуватися тільки на конкретне поле контексту, а не на все value.

Підсумок

Оновлення контексту викликає ререндер усіх споживачів, тому що React підписує їх на одне спільне значення і відстежує його за посиланням.

Це поведінка за дизайном - Context не призначений для "дрібночастотних" даних.

Щоб оптимізувати:

  • мемоізуй value,
  • ділі контексти,
  • використовуй спеціалізовані рішення (use-context-selector, Zustand, Jotai тощо)

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

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

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