Предшественники
Redux имеет смешанное наследство. Он схож с некоторыми паттернами и технологиями, но, в то же время, значительно отличается от них. Мы раскроем суть этих общностей и различий далее.
Flux
Может ли Redux считаться реализацией Flux?
(Не беспокойтесь, создатели Flux одобрили Redux, если это все, что вы хотели знать.)
Redux черпал вдохновление из некоторых важных качеств Flux. Как и Flux, Redux предписывает вам концентрировать вашу логику обновления модели на определенном уровне вашего приложения (“стор” в Flux, “редюсеры” в Redux). Вместо того, чтобы код приложения изменял данные напрямую, оба предлагают вам описывать каждое изменение, как простой объект, называемый "экшеном".
В отличие от Flux, Redux не имеет концепции Диспетчера. Это потому, что он опирается на чистые функции вместо генераторов событий (event emitters) и чистые функции легко создаются и не требуют дополнительной сущности для управления ими. В зависимости от того, как вы видите Flux, вы можете считать это отклонением или деталью реализации. Flux часто может быть описан как (state, action) => state
. В этой трактовке Redux соответствует архитектуре Flux, но делает ее проще благодаря чистым функциям.
Другим важным отличием от Flux является то, что Redux предполагает, что вы никогда не изменяете ваши данные напрямую. Вы запросто можете использовать простые объекты и массивы для состояния, но строго рекомендуется изменять их через редюсеры. Вы всегда должны возвращать новый объект, что проще сделать при помощи object spread синтаксиса, предлагаемого ES7 и внедренного в Babel или библиотеки типа Immutable.
Несмотря на то что технически возможно писать нечистые редюсеры, которые изменяют данные с целью сохранения производительности, мы категорически не советуем вам этого делать. Функциональности типа time travel, record/replay или hot reloading сломаются. Кроме того, вряд ли иммутабельность создает проблемы производительности в большинстве реальных приложений, потому что, как показывает Om, даже если вы потеряете на создании новых объектов, вы все равно выиграете, избегая дорогостоящих перерисовок и повторных расчетов, т.к. вы точно знаете, что изменилось, благодаря чистоте редюсера.
Elm
Elm — это функциональный язык программирования, вдохновленный Haskell, созданный Evan Czaplicki. Он обеспечивает соблюдение архитектуры “модель представление обновление”, где обновление имеет следующую сигнатуру: (state, action) => state
. Технически, “обновления” Elm эквивалентны редюсерам в Redux.
Но, в отличие от Redux, Elm — это язык, что дает много дополнительных преимуществ вроде принудительной чистоты, статической типизации, иммутабельности из коробки и сопоставления с образцом (используя case
выражения). Даже если вы не планируете использовать Elm, вы должны ознакомиться с его архитектурой и поиграть с ним. Существует интересная JavaScript-библиотека, реализующая похожие идеи. Стоит взглянуть на нее для вдохновения на Redux! Одним из способов, с помощью которого мы можем приблизиться к статической типизации Elm, является использование инструментов для опциональной типизации наподобие Flow.
Immutable
Immutable — это JavaScript-библиотека, позволяющая использовать неизменяемые структуры данных. Она мощная и имеет богатый JavaScript API.
Immutable и большинство похожих библиотек ортогональны Redux. Не стесняйтесь использовать их вместе.
Для Redux не важно, как вы храните состояние — простым объектом, объектом Immutable или как-то еще. Скорее всего, Вам понадобится механизм (де)сериализации состояний для написания универсальных приложений (universal apps) и получения их состояния с сервера, но, кроме того, вы можете использовать любую библиотеку для сохранения данных до тех пор, пока она поддерживает иммутабельность. Например, не имеет смысла использовать Backbone для Redux-состояний, поскольку модели Backbone — мутабельны.
Обратите внимание, что даже если ваша иммутабельная библиотека поддерживает курсоры, вам не стоит использовать ее в Redux-приложении. Все дерево состояний должно рассматриваться как объект, доступный только для чтения, и вы должны использовать Redux для обновления состояния и для подписки на обновления. Поэтому запись с помощью курсора не имеет смысла для Redux. Если вы используете курсоры только для разделения дерева состояния данных и дерева UI и постепенного улучшения курсоров, вам следует использовать селекторы вместо курсоров. Селекторы — это комбинируемые функции-геттеры. Изучите reselect для по-настоящему мощной и лаконичной реализации комбинируемых селекторов.
Baobab
Baobab — это еще одна популярная библиотека, реализующая иммутабельное API для обновления JavaScript-объектов. Несмотря на то что вы можете использовать ее с Redux, от их совместного использования будет мало толку.
Большинство функциональности Baobab обеспечивается посредством обновления данных через курсоры, но Redux настаивает, что единственный способ обновить данные — это генерация и распространение экшенов (dispatch actions). Следовательно, Redux и Baobab стремятся решить одну и ту же задачу по-разному и не дополняют друг друга.
В отличии от Immutable, Baobab пока не реализует никаких специальных эффективных структур данных под капотом, так что вы на самом деле ничего не выиграете от использования его вместе с Redux. Проще использовать обычные объекты в данном случае.
Rx
Reactive Extensions (и ее современная реализация) — это превосходный способ управлять сложностью асинхронных приложений. На самом деле существует даже попытка создать библиотеку, которая моделирует взаимодействие человека и компьютера, как взаимозависимых наблюдателей.
Имеет ли смысл использовать Redux вместе с Rx? Конечно! Они отлично работают вместе. Например, легко сделать Redux-стор наблюдаемым:
function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = store.subscribe(() => onNext(store.getState()))
onNext(store.getState())
return { dispose }
}
}
}
Кроме того, вы можете создавать различные асинхронные потоки, чтобы преобразовать их в экшены перед подачей их на store.dispatch()
.
Вопрос в том, действительно ли вами необходим Redux, если вы уже используете Rx? Возможно, нет. Несложно внедрить Redux в Rx. Некоторые скажут, что сделать это проще простого, используя Rx .scan()
метод. Это вполне возможно!
Если вы сомневаетесь, изучите исходный код Redux (там не так много всего), а также его экосистему (например, developer tools). Если вы не слишком переживаете, но хотите всецело заняться реактивным программированием, вам, возможно, захочется исследовать что-то вроде Cycle или даже связать его вместе с Redux. Расскажете нам, что у вас получилось!