В React-комьюнити постоянно кипят споры о том, что лучше: Context или Redux. Для меня эта тема особенно актуальна, ведь я постоянно работаю с этими инструментами. К сожалению, большая часть этих "дебатов" возникает из-за непонимания того, для чего на самом деле предназначены Context и Redux, и как их правильно использовать.
Я лично много раз отвечал на вопросы о Context и Redux в интернете, и, честно говоря, эта путаница меня немного расстраивает. Поэтому я решил поделиться своим опытом и знаниями в этой статье, чтобы раз и навсегда прояснить ситуацию.
Если коротко
Context и Redux — это одно и то же? Нет, конечно! Это разные инструменты, которые решают разные задачи.
Context — это инструмент управления состоянием? Нет. Context — это механизм внедрения зависимостей. Он просто "транспортирует" данные, но сам по себе ничего не "управляет". Управление состоянием — это уже ваша ответственность, и обычно для этого используются useState или useReducer.
Context вместе с useReducer заменяют Redux? Не совсем. У них есть точки соприкосновения, но есть и существенные различия в функциональности.
Когда использовать Context? Всегда, когда нужно предоставить доступ к какому-то значению для части дерева React-компонентов, не передавая это значение через props на каждом уровне.
Когда использовать Context вместе с useReducer? Когда у вас есть относительно сложная логика управления состоянием React-компонентов в определенном разделе приложения.
Когда использовать Redux? Redux особенно полезен, когда:
У вас много состояния приложения, которое используется в разных частях приложения.
Состояние приложения часто обновляется.
Логика обновления состояния может быть сложной.
Приложение имеет средний или большой размер, и над ним работает несколько человек.
Вам нужно понимать, когда, почему и как обновлялось состояние приложения, а также визуализировать изменения состояния во времени.
Вам нужны мощные инструменты для управления побочными эффектами, сохранения данных и сериализации.
Разбираемся в Context и Redux
Для правильного использования любого инструмента важно понимать его суть, проблему, которую он решает, и историю его создания. Также важно понимать, какие проблемы вы пытаетесь решить в своем приложении, и выбирать инструменты, которые подходят для этого лучше всего.
Большая часть путаницы вокруг "противостояния" Context и Redux возникает из-за непонимания того, что на самом деле делают эти инструменты, и какие проблемы они решают. Поэтому, чтобы действительно знать, когда их использовать, нам нужно четко определить, что они делают, и какие проблемы решают.
Что такое React Context?
Давайте начнем с описания Context из официальной документации React:
Context предоставляет способ передавать данные через дерево компонентов без необходимости передавать пропсы (props) вручную на каждом уровне.
В типичном React-приложении данные передаются сверху вниз (от родительского к дочернему) через пропсы (props), но это может оказаться избыточным для определенных типов проп (например, локализации, UI-темы), которые требуются для многих компонентов в приложении. Контекст предоставляет способ делиться подобными значениями между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.
Обратите внимание, что здесь ничего не говорится об "управлении" значениями — только о "передаче" и "способе делиться" значениями.
Текущий API React Context (React.createContext()) был впервые выпущен в React 16.3. Он заменил устаревший Context API, который был доступен с ранних версий React, но имел серьезные недостатки в проектировании. Основная проблема с устаревшим контекстом заключалась в том, что обновления значений, передаваемых через контекст, могли быть "заблокированы", если компонент пропускал рендеринг через shouldComponentUpdate. Поскольку многие компоненты полагались на shouldComponentUpdate для оптимизации производительности, это делало устаревший контекст бесполезным для передачи простых данных. createContext() был разработан для решения этой проблемы, поэтому любое обновление значения будет видно в дочерних компонентах, даже если компонент в середине пропускает рендеринг.
Использование Context
Чтобы использовать React Context в приложении, нужно сделать следующие шаги:
Сначала вызвать const MyContext = React.createContext(), чтобы создать экземпляр объекта контекста.
В родительском компоненте в методе render написать . Это помещает некоторую часть данных в контекст. Данное значение может быть чем угодно — строкой, числом, объектом, массивом, экземпляром класса, эмиттером событий и т.д.
Затем в любом компоненте, вложенном в этот provider, вызвать const theContextValue = useContext(MyContext).
Каждый раз, когда родительский компонент рендерится и передает новую ссылку для context provider как значение value, любой компонент, который читает из этого контекста, будет вынужден перерендериться.
Чаще всего value контекста — это то, что приходит из состояния React-компонента, например:
JavaScript
function ParentComponent() {
const [counter, setCounter] = useState(0);
// создается объект, содержащий и значение, и сеттер
const contextValue = { counter, setCounter };
return (
);
}
Затем дочерний компонент может вызвать useContext и прочитать значение:
JavaScript
function NestedChildComponent() {
const { counter, setCounter } = useContext(MyContext);
// сделай что-нибудь со значением и сеттером counter
}
Назначение и варианты использования Context
Исходя из этого, мы можем видеть, что Context вообще ничем не "управляет". Вместо этого это похоже на трубу или червоточину. Вы помещаете что-то в верхний конец канала с помощью , и оно (что бы это ни было) проходит по каналу, пока не выскочит на другой конец, где другой компонент запрашивает его с помощью useContext(MyProvider).
Итак, основная цель использования Context — избежать "prop-drilling". Вместо того, чтобы передавать это значение в качестве пропа, явно через каждый уровень дерева компонентов, который в нем нуждается, любой компонент, вложенный в , может просто сказать useContext(MyContext), чтобы получить значение по мере необходимости. Это действительно упрощает код, потому что нам не нужно писать лишнюю логику, пробрасывая проп.
Концептуально это форма "Dependency Injection". Мы знаем, что дочернему компоненту требуется значение определенного типа, но он не пытается самостоятельно создать это значение. Вместо этого предполагается, что какой-то родительский компонент пробросит это значение во время выполнения.
Что такое Redux?
Для сравнения давайте посмотрим на описание в документации из "Redux Essentials" туториала в оригинальных доках:
Redux — это паттерн и библиотека для управления и обновления состояния приложения с использованием событий, называемых "экшенами". Он обеспечивает централизованное хранилище состояния, которое необходимо использовать во всем приложении, с правилами, гарантирующими, что состояние может обновляться только предсказуемым образом.
Redux помогает вам управлять "глобальным" состоянием — состоянием, которое необходимо во многих частях вашего приложения.
Паттерны и инструменты, предоставляемые Redux, упрощают понимание того, когда, где, почему и как обновляется состояние в вашем приложении, и как логика вашего приложения будет вести себя, когда эти изменения произойдут.
Обратите внимание, что это описание:
конкретно относится к "управлению состоянием"
говорит, что цель Redux — помочь вам понять, как состояние меняется с течением времени
Исторически Redux создавался как реализация "архитектуры Flux", которая была паттерном, впервые предложенным Facebook в 2014 году, через год после выхода React. После этого объявления сообщество создало десятки вдохновленных Flux библиотек с различными подходами к концепциям Flux. Redux вышел в 2015 году и быстро выиграл "Flux войну", потому что у него была лучшая концепция, он соответствовал задачам, которые люди пытались решить, и отлично работал с React.
Top comments (0)