Управление состоянием — одна из важнейших задач, решаемых в разработке на React. Было создано множество инструментов в помощь разработчикам для решения этой задачи. Наиболее популярным инструментом является Redux — небольшая библиотека, созданная Дэном Абрамовым, и предназначенная помочь разработчикам использовать паттерн проектирования Flux в их приложениях. В этой статье мы разберем, действительно ли нам нужен Redux, и посмотрим, как мы можем заменить его более простым подходом, в основе которого лежат Observable и React Hooks.  Redux так часто ассоциируется с React, что многие разработчики используют его, не задумываясь зачем им нужен именно Redux. React позволяет легко синхронизировать компонент и его состояние с помощью setState() / useState(). Но все становится сложнее, как только состояние начинает использоваться сразу несколькими компонентами. Самое очевидное решение совместного использования общего состояния между несколькими компонентами — это переместить его (состояние) к их общему родителю. Но такое решение «в лоб» очень быстро может привести к сложностям: если компоненты находятся далеко друг от друга в иерархии компонентов, то для обновления общего состояния потребуется множество пробрассываний через свойства компонентов. React Context может помочь уменьшить количество пробрассываний, но объявление нового контекста каждый раз, когда какое-либо состояние начинает использоваться совместно с еще одним компонентом, потребует все больших усилий и в конце концов может привести к ошибкам. Redux решает эти проблемы, представляя объект Store, который содержит целиком все состояние приложения. В компоненты, которым требуется доступ к состоянию, этот Store внедряется с помощью функции connect. Также эта функция гарантирует, что при изменении состояния все компоненты, зависящие от него, будут перерисованы. Наконец, чтобы изменить состояние, компоненты должны отправлять action, которые запускают reducer для вычисления нового измененного состояния. Когда в первый раз понял концепции Redux Впервые прочитав официальный туториал по Redux, меня больше всего поразил большой объем кода, который мне пришлось написать, чтобы изменить состояние. Изменение состояния требует объявления нового action, реализации соответствующего reducer и, наконец, отправки action. Redux также поощряет написание action creator, чтобы облегчить создание действия каждый раз, когда вы хотите его отправить. Всеми этими действиями Redux усложняет понимание кода, его рефакторинг и отладку. При чтении кода, написанного кем-то другим, часто бывает трудно выяснить что выполняется при отправке action. Для начала нам придется погрузиться в код action creator, чтобы найти соответствующий тип действия, а затем найти reducers, которые обрабатывают этот тип действия. Все может стать еще сложнее, если используются некоторые middlewares, как, например, redux-saga, что делает контекст решения еще более неявным. И, наконец, при использовании TypeScript, Redux может разочаровать. По замыслу action — это просто строки, связанные с дополнительными параметрами. Существуют способы написания хорошо типизированного кода Redux с помощью TypeScript, но это может быть очень утомительно и опять же может привести к увеличению объема кода, который нам придется написать. Ощущения от изучения кода написанного с помощью Redux Чтобы решить проблему совместного использования состояния более простым способом, нам прежде всего нужно найти способ уведомления компонентов при изменении общего состояния другими компонентами. Чтобы сделать это, давайте создадим класс Observable, который содержит единственное значение и который позволяет компонентам подписываться на изменения этого значения. Метод подписки будет возвращать функцию, которую необходимо вызвать, чтобы отписаться от Observable. На TypeScript реализовать такой класс довольно просто: Если сравнить этот класс c Redux Store, вы увидите, что они довольно похожи: get() соответствует getState(), а subscribe() — то же самое. Основное отличие заключается в методе dispatch(), который был заменен более простым методом set(), позволяющим изменять содержащееся в нем значение без необходимости полагаться на reducer. Другое существенное отличие состоит в том, что, в противоположность Redux, мы будем использовать множество Observable вместо одного Store, содержащего всё состояние. Теперь Observable можно применить для хранения общего состояния, но нам все еще нужно переместить логику, содержащуюся в reducer. Для этого мы используем концепцию сервисов. Сервисы — это классы, которые реализуют всю бизнес-логику наших приложений. Давайте попробуем переписать reducer Todo из туториала Redux в сервис Todo, используя Observable: Сравнивая это с reducer Todo, мы можем отметить следующие различия: Теперь, когда мы заменили «store и reducer из Redux» на «Observable и сервисы», нам нужно сделать сервисы доступными из всех компонентов React. Есть несколько способов сделать это: мы могли бы использовать IoC фреймворк, например, Inversify; использовать контекст React или применить тот же подход, что и в Store Redux, – один глобальный экземпляр для каждого сервиса. В этой статье мы рассмотрим последний подход: Теперь мы можем получить доступ к общему состоянию и изменить его из всех наших компонентов React, импортировав экземпляр todoService. Но нам все еще нужно найти способ перерисовки наших компонентов, когда общее состояние изменяется другим компонентом. Чтобы это сделать, мы напишем простой hook, который добавляет переменную состояния к компоненту, подписывается на Observable и обновляет переменную состояния, когда значение Observable изменяется: Наш инструментарий готов. Мы можем использовать Observable для хранения общего состояния в сервисах и использовать useObservable, чтобы гарантировать, что компоненты всегда будут синхронизированы с этим состоянием. Давайте перепишем компонент TodoList из учебника Redux, используя новый hook: Как мы видим, мы написали несколько компонентов, которые обращаются к значениям общего состояния (todos и visibilityFilter). Эти значения изменяются просто путем вызова методов из todoService. Благодаря hook useObservable, который подписывается на изменения значений, эти компоненты автоматически перерисовываются при изменении общего состояния. Если мы сравним этот код с подходом Redux, то увидим несколько преимуществ: Redux, конечно, по-прежнему имеет некоторые серьезные преимущества, особенно Redux DevTools, позволяющие разработчикам наблюдать за изменениями состояния во время разработки и перемещаться во времени к прошлым состояниям приложения, что может быть отличным инструментом для отладки. Но по моему опыту, я редко использовал это, и цена, которую нужно заплатить, кажется слишком высокой для небольшой выгоды. Во всех наших приложениях React и React Native мы с успехом использовали подход, аналогичный описанному в этой статье. На самом деле мы никогда не чувствовали необходимости в более сложной системе управления состоянием, чем эта. Класс Observable, представленный в этом посте, довольно прост. Его можно заменить более продвинутыми реализациями, такими как micro-observables (наша собственная библиотека) или RxJS. Представленное здесь решение очень похоже на то, что может быть достигнуто с MobX. Основное отличие состоит в том, что MobX поддерживает глубокую мутабельность состояния объектов. Он также полагается на прокси-серверы ES6, чтобы уведомлять об изменениях, делая неявным повторный рендеринг, и усложняя отладку, когда все работает не так, как ожидалось. Вдобавок MobX не очень хорошо работает с асинхронными функциями. Разработчик