Ререндер дочірніх елементів
Коротко
Коли змінюється значення, передане в <Context.Provider value={...}>,
React перемальовує всі компоненти, які використовують цей контекст через useContext() або <Context.Consumer>.
Чому це відбувається
Тому що Context - це глобальний механізм підписки.
React робить приблизно наступне:
- У кожного контексту є список споживачів (components subscribed).
- Коли Provider отримує нове значення
value, React порівнює нове значення з попереднім (за посиланням). - Якщо посилання змінилося (
!==), він сигналізує всім підписникам, що "значення змінилося". - Кожен компонент, що використовує
useContext(), виконує повторний рендер, щоб отримати нове значення.
Головне: порівняння йде за посиланням, не за вмістом
<ThemeContext.Provider value={{ mode: "dark" }}>
<App />
</ThemeContext.Provider>Навіть якщо mode не змінився, при кожному рендері App створюється новий об'єкт
({ mode: "dark" } !== { mode: "dark" }), і React думає, що значення контексту змінилося -> всі підписники перемальовуються.
Як цього уникнути - мемоізація value
Потрібно передавати стабільне посилання за допомогою useMemo:
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 |
Способи оптимізації
- Розділяй контексти
Не зберігай усе в одному великому контексті - створюй незалежні:
AuthContext,ThemeContext,UIContextтощо. - Використовуй
useMemoдля value
const value = useMemo(() => ({ user, setUser }), [user]);- Ізолюй "важких" споживачів
Якщо контекст часто оновлюється - обгортай частину UI в
memoабо виноси окремі Providers. - Використовуй селектори (наприклад, з бібліотекою use-context-selector)
Вона дозволяє компоненту підписуватися тільки на конкретне поле контексту, а не на все
value.
Підсумок
Оновлення контексту викликає ререндер усіх споживачів, тому що React підписує їх на одне спільне значення і відстежує його за посиланням.
Це поведінка за дизайном - Context не призначений для "дрібночастотних" даних.
Щоб оптимізувати:
- мемоізуй
value, - ділі контексти,
- використовуй спеціалізовані рішення (
use-context-selector, Zustand, Jotai тощо)
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.