Нет. Это разные инструменты, делающие разные вещи и используемые в разных целях.
Нет. Контекст — это форма внедрения зависимостей (dependency injection). Это транспортный механизм, который ничем не управляет. Любое «управление состоянием» осуществляется вручную, как правило, с помощью хуков useState()/useReducer().
Нет. Они в чем-то похожи и частично пересекаются, но сильно отличаются в плане возможностей.
Когда вы хотите сделать некоторые данные доступными для нескольких компонентов, но не хотите передавать эти данные в виде пропов на каждом уровне дерева компонентов.
Когда вам требуется управление состоянием умеренно сложного компонента в определенной части приложения.
Redux наиболее полезен в следующих случаях:
Для правильного использования инструмента критически важно понимать:
Также важно понимать, какие задачи вы пытаетесь решить в вашем приложениии, и использовать те интструменты, которые решают их наилучшим образом, а не потому, что кто-то сказал вам их использовать, и не потому, что они являются популярными, а потому, что они лучше всего подходят вам в определенной ситуации.
Неразбериха вокруг Context и Redux связана, в первую очередь, с непониманием того, для чего данные инструменты предназначены, и какие задачи они решают. Поэтому, прежде чем говорить о том, когда их следует использовать, необходимо определить, что они из себя представляют и какие проблемы решают.
Начнем с определения контекста из официальной документации:
«Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропы на промежуточных уровнях.
В типичном React-приложении данные передаются сверху вниз (от предка к потомку) с помощью пропов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропов (например, выбранный язык, тема интерфейса), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ распределять такие данные между компонентами без необходимости явно передавать пропы на каждом уровне дерева компонентов».
Обратите внимание, в данном определении ни слова не говорится об «управлении», только о «передаче» и «распределении».
Текущее API контекста (React.createContext()) впервые было представлено в React 16.3 в качестве замены устаревшего API, доступного в ранних версиях React, но имеющего несколько недостатков дизайна. Одной из главных проблемой являлось то, что обновления значений, переданных через контекст, могли быть «заблокированы», если компонент пропускал рендеринг через shouldComponentUpdate(). Поскольку многие компоненты прибегали к shouldComponentUpdate() в целях оптимизации, передача данных через контекст становилась бесполезной. createContext() был спроектирован для решения этой проблемы, поэтому любое обновление значения отразится на дочерних компонентах, даже если промежуточный компонент пропускает рендеринг.
Использование контекста в приложении предполагает следующее:
При обновлении родительского компонента и передачи новой ссылки в качестве значения провайдера, любой компонент, «потребляющий» контекст, будет принудительно обновлен.
Обычно, значением контекста является состояние компонента:
import { createContext } from 'react'
export const MyContext = createContext()
export function ParentComponent({ children }) {
const [counter, setCounter] = useState(0)
return (
<MyContext.Provider value={[counter, setCounter]}>
{children}
</MyContext.Provider>
)
}
После этого дочерний компонент может вызвать хук useContext() и прочитать значение контекста:
import { useContext } from 'react'
import { MyContext } from './MyContext'
export function NestedChildComponent() {
const [counter, setCounter] = useContext(MyContext)
// ...
}
Мы видим, что контекст, в действительности, ничем не управляет. Вместо этого, он представляет собой своего рода тоннель (pipe). Вы помещаете данные в начало (наверх) тоннеля с помощью <MyContext.Provider>, затем эти данные опускаются вниз до тех пор, пока компонет не запросит их с помощью useContext(MyContext).
Таким образом, основная цель контекста состоит в предотвращении «бурения пропов» (prop-drilling). Вместо передачи данных в виде пропов на каждом уровне дерева компонентов, любой компонент, вложенный в <MyContext.Provider>, может получить к ним доступ посредством useContext(MyContext). Это избавляет от необходимости писать код, реализующий логику передачи пропов.
Концептуально, это является формой внедрения зависимостей. Мы знаем, что потомок нуждается в данных определенного типа, но он не пытается создавать или устанавливать эти данные самостоятельно. Вместо этого, он полагается на то, что некоторый предок передаст эти данные во время выполнения (runtime).
Вот о чем гласит определение из «Основ Redux»:
«Redux — это паттерн проектирования и библиотека для управления и обновления состояния приложения, использующая события, именуемые операциями (действиями, actions). Redux выступает в роли централизованного хранилища состояния приложения, следюущего правилам, позволяющим обеспечить предсказуемое обновление состояние.
Redux позволяет управлять „глобальным“ состоянием — состоянием, которое трубется нескольким частям приложения.
Паттерны и инструменты, предоставляемые Redux, позволяют легче определять где, когда, почему и как было обновлено состояние, и как отреагировало приложение на это изменение».
Обратите внимание, что данное описание указывает на:
Изначально Redux являлся реализацией «архитектуры Flux», паттерна проектирования, разработанного Facebook в 2014 году, через год после появления React. После появления Flux, сообщество разработало множество библиотек, по-разному реализующих данную концепцию. Redux появился в 2015 году и быстро стал победителем в этом соревновании благодаря продуманному дизайну, решению наиболее распространенных проблем и отличной совместимости с React.
Архитектурно, Redux подчеркнуто использует принципы функционального программирования, что позволяет писать код в форме предсказуемых «функций-редукторов» (reducers), и обособлять идею «какое событие произошло» от логики, определяющей «как обновляется состояние при возгникновении данного события». В Redux также используется промежуточное программное обеспечение (middleware) как способ расширения возможностей хранилища, включая обработку побочных эффектов (side effects).
Redux также предоставляет инструменты разработчика, позволяющие изучать историю операций и изменение состояния в течение времени.
Сам по себе Redux не зависит от UI — вы можете использовать его с любым слоем представления (view layer) (React, Vue, Angular, ванильный JS и т.д.) либо без UI вообще.
Однако, чаще всего, Redux используется совместно с React. Библиотека React Redux — это официальный связывающий слой UI, позволяющий React-компонентам взаимодействовать с хранилищем Redux, получая значения из состояния Redux и инициализируя выполнение операций. React-Redux использует контекст в своих внутренних механизмах. Тем не менее, следует отметить, что React-Redux передает через контекст экземпляр хранилища Redux, а не текущее значение состояния! Это пример использования контекста для внедрения зависимостей. Мы знаем, что наши подключенные к Redux компоненты нуждаются во взаимодействии с хранилищем Redux, но мы не знаем или нам неважно, что это за хранилище, когда мы определяем компонент. Настоящее хранилище Redux внедряется в дерево во время выполнения с помощью компонента <Provider>, предоставляемого React-Redux.
Следовательно, React-Redux также может быть использован для предотвращения «бурения» (по причине внутреннего использования контекста). Вместо явной передачи нового значения через <MyContext.Provider>, мы можем поместить эти данные в хранилище Redux и затем получить их в нужном компоненте.
Основное назначение Redux согласно официальной документации:
«Паттерны и инструменты, предоставляемые Redux, облегчают понимание того, когда, где, почему и как произошло изменение состояния, а также того, как на это отреагировало приложение».
Существует еще несколько причин использования Redux. Одной из таких причин является предотвращение «бурения».
Другие случаи использования:
Дэн Абрамов перечислил эти случаи в статье 2016 года «Почему вам может быть не нужен Redux».
Состояние — это любые данные, описывающие поведение приложения. Мы можем разделить состояние на такие категории, как состояние сервера, состояние коммуникаций и локальное состояние, если хотим, но ключевым аспектом является хранение, чтение, обновление и использование данных.
«Управление состоянием — это изменение состояния в течение времени».
Таким образом, мы можем сказать, что «управление состоянием» означает следующее:
Также, как правило, существует способ получения уведомлений об изменении текущего значения состояния.
React-хуки useState() и useReducer() являются отличными примерами управления состоянием. С помощью этих хуков мы можем:
Redux и MobX также позволяют управлять состоянием:
К инструментам управления состоянием можно причислить даже инструменты для работы с кэшем сервера, такие как React-Query, SWR, Apollo и Urql — они сохраняют начальное значение на основе полученных (fetched) данных, возвращают текущее значение с помощью хуков, позволяют обновлять значения посредством «серверных мутаций» и уведомляют об изменениях с помощью повторного рендеринга компонента.
Как было отмечено ранее, контекст сам по себе ничего не хранит. За передачу значения, которое, обычно, зависит от состояния компонента, в контекст отвечает родительский компонент, который рендерит <MyContext.Provider>. Настоящее «управление состоянием» происходит при использовании хуков useState()/useReducer().
David Khourshid также отмечает:
«Контекст — это то, как существующее состояние распределяется между компонентами. Контекст ничего не делает с состоянием».
«Полагаю, контекст — это как скрытые пропы, абстрагирующие состояние».
Все, что делает контекст, это позволяет избежать «бурения».
Сравним возможности контекста и React+Redux:
Очевидно, что это совершенно разные инструменты с разными возможностями. Единственной точкой пересечения между ними является предотвращение «бурения».
Одной из проблем в дискуссии «Context против Redux» является то, что люди, зачастую, на самом деле имеют ввиду следующее: «Я использую useReducer() для управления состоянием и контекст для передачи значения». Но, вместо этого, они просто говорят: «Я использую контекст». В этом, на мой взгляд, кроется основная причина неразберихи, способствующая поддержанию мифа о том, что контекст «управляет состоянием».
Рассмотрим комбинацию Context + useReducer(). Да, такая комбинация выглядит очень похоже на Redux + React-Redux. Обе эти комбинации имеют:
Тем не менее, между ними по-прежнему существуют некоторые важные отличия, проявляющиеся в их возможностях и поведении. Я отметил эти отличия в статьях «Поведение React, Redux и Context» и “(Почти) полное руководство по рендерингу в React”. Суммируя, можно отметить следующее:
Существуют и другие важные отличия:
Вот, что сказал Sebastian Markbage (архитектор команды ядра React) об использовании контекста:
«Мое личное мнение состоит в то, что новый контекст готов к использованию для маловероятных обновлений с низкой частотой (таких как локализация или тема). Он также может использоваться во всех тех случаях, в которых использовался старый контекст, т.е. для статических значений с последующим распространением обновления по подпискам. Он не готов к использованию в качестве замены Flux-подобных „распространителей“ состояния».
В сети существует много статей, рекомендующих настройку нескольких отдельных контекстов для разных частей состояния, что позволяет избежать ненужных повторных рендерингов и решить проблемы, связанные с областями видимости. Некоторые из постов также предлагают добавлять собственные «компоненты по выбору контекста», что требует использования сочетания React.memo(), useMemo() и аккуратного разделения кода на два контекста для каждой части приложения (одна для данных, другая для функций обновления). Безусловно, код можно писать и так, но в этом случае вы заново изобретаете React-Redux.
Таким образом, несмотря на то, что Context + useReducer() — это легкая альтернатива Redux + React-Redux в первом приближении… эти комбинации не идентичны, контекст + useReducer() не может полностью заменить Redux!
Для выбора правильного инструмента очень важно понимать, какие задачи решает инструмент, а также какие задачи стоят перед вами.
Еще раз: названные инструменты решают разные задачи!
Как же решить, что следует использовать?
Для этого вам всего лишь нужно определить, какой инструмент наилучшим образом решает задачи вашего приложения.
Я считаю, что если в вашем приложении имеется 2-3 контекста для управления состоянием, то вам следует переключиться на Redux.
Часто можно услышать, что «использование Redux предполагает написание большого количества шаблонного кода», однако, «современный Redux» значительно облегчает изучение данного инструмента и его использование. Официальный пакет Redux Toolkit решает проблему шаблонизации, а хуки React-Redux упрощают использование Redux в компонентах React.
Разумеется, добавление RTK и React-Redux в качестве зависимостей увеличивает «бандл» приложения по сравнению с контекстом + useReducer(), которые являются встроенными. Но преимущества такого подхода перекрывают недостатки — лучшая трассировка состояния, простая и более предсказуемая логика, улучшенная оптимизация рендринга компонентов.
Также важно отметить, что одно не исключает другого — вы можете использовать Redux, Context и useReducer() вместе. Мы рекомендуем хранить «глобальное» состояние в Redux, а локальное — в компонентах и внимательно подходить к определению того, какая часть приложения должна храниться в Redux, а какая — в компонентах. Так что вы можете использовать Redux для хранения глобального состояния, Context + useReducer() — для хранения локального состояния, и Context — для статических значений, одновременно и в одном приложении.
Еще раз: я не утверждаю, что все состояние приложения должно храниться в Redux или что Redux — это всегда лучшее решение. Я утверждаю лишь, что Redux — хороший выбор, существует много причин использовать Redux, и плата за его использование не так высока, как многие думают.
Наконец, контекст и Redux не единственные в своем роде. Существует множество других инструментов, решающих иные аспекты управления состоянием. MobX — популярное решение, использующее ООП и наблюдаемые объекты (observables) для автоматического обновления зависимостей. Среди других подходов к обновлению состояния можно назвать Jotai, Recoil и Zustand. Библиотеки для работы с данными, вроде React Query, SWR, Apollo и Urql, предоставляют абстракции, упрощающие применение распространенных паттернов для работы с состоянием, кэшируемым сервером (скоро похожая библиотека (RTK Query) появится и для Redux Toolkit).
Надеюсь, данная статья помогла вам понять разницу между контекстом и Redux, а также какой инструмент и в каких случаях следует использовать. Благодарю за внимание.
OpenAI готовится запустить собственную поисковую систему на базе ChatGPT. Информацию об этом публикуют западные издания. Ожидается, что новый поисковик может…
Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…
Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…
Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…
Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…
У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…