Комментарии 6
Около 8-и лет назад я открыл для себя MobX и с тех пор компоненты ререндерятся точечно и оптимально благодаря реактивности. В 2026-м на хабре люди всё ещё разбираются как оптимизировать контекст
MobX отлично решает эту задачу, и если он уже есть в проекте, то городить свой контекст незачем.
Но часто бывает, что вы работаете в чужом проекте, где уже выбран другой подход, например Redux, а вам нужно обернуть ветку, которая будет масштабироваться. В Redux такое неудобно: стор один на всё приложение, и для каждого экземпляра ветки пришлось бы заводить своё состояние внутри него, с ключом экземпляра. А второй стейт-менеджер ради одной ветки тянуть тоже не хочется.
Плюс вторая половина статьи не только про Context: механизм защиты от tearing в useSyncExternalStore нужен любому внешнему стору, а не только самописному.
Обычный приём 1. Если провайдер ререндерился по посторонней причине, например вместе с родителем, ссылка на
valueостанется прежней, и React вообще не запустит обновление попотребителей.
Потребители всё равно обновятся из-за ререндера родителя (исключением будут только memo компоненты). И если не оборачивать value в useMemo, то контекст не вызовет повторный ререндер потребителей вслед за ререндером родителя. Поэтому в общем случае мемоизировать value бессмысленно.
Если JSX потребителей создаётся прямо в ререндерящемся компоненте, то да, они обновятся каскадом и useMemo ничего не даст. Но это не единственный сценарий. Если между провайдером и потребителем есть мемо-граница, каскад на ней останавливается. Самая частая граница даже не React.memo, а обычные
children:
function App() {
return <Layout><Smt /></Layout>;
}
function Layout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
return <UserProvider>{children}</UserProvider>;
}
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = { user, setUser };
return <Ctx value={value}>{children}</Ctx>;
}
Меняем sidebarOpen. Ререндерятся Layout и UserProvider, но <Smt /> создал App, который не ререндерился. Ссылка на элемент та же, и React каскадом в Smt не пойдёт. А value получился новым объектом, поэтому все, кто внутри Smt читает Ctx, всё равно перерендерятся, теперь уже только
из-за контекста. С useMemo(() => ({ user, setUser }), [user]) этого не происходит.
С React Compiler таких границ становится ещё больше: он мемоизирует JSX сам. Так что не бессмысленно, а зависит от дерева.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
React Context без лишних ререндеров: подписка на отдельные поля