В преддверии конференции HolyJS мы поговорили с Максом Штойбером (одним из организаторов React.js Vienna Meetup, создателем react-boilerplate, принимавшим участие и в создании Carte Blanche) и Сергеем Лапиным (членом программного комитета HolyJS) и обсудили, как выбрать state management решение.
Redux, MobX, Relay или другая реализация Flux? Практические рекомендации и лучшие практики.
Макс Штойбер
– Макс, привет! Для начала коротко представься и расскажи о себе.
– Привет! Меня зовут Макс Штойбер, я Open Source разработчик, работаю в компании Thinkmill. Мы делаем всякие прикольные штуки вроде KeystoneJS, разрабатываем веб- и мобильные приложения, вносим свой вклад в улучшение User Experience.
– Звучит круто! Расскажи, как давно ты всем этим занимаешься, когда открыл для себя React, и в каких проектах участвовал?
– Хмм, кажется, я с React'ом уже примерно 2,5 года. Когда я начинал, Flux считалась самой «горячей» темой в мире! Пожалуй, наиболее известные проекты, в которых я принимал участие, это KeystoneJS, react-boilerplate и sharingbuttons.io.
От Flux к Redux
– Думаю, что теперь у читателей сложилось впечатление о том, кто перед ними. Как ты знаешь, наша основная тема сегодня — это «React Redux на длинных дистанциях: нужен ли он в больших проектах?». Давай начнем с того, что ты думаешь о концепции Flux и какие реализации успел попробовать на сегодняшний день.
– Я очень люблю Flux! Думаю, это восхитительная идея — сделать поток данных однонаправленным.
Это настолько упрощает работу с состоянием приложения и облегчает жизнь, что сложно поверить, что когда-то было иначе. Flux прекрасно масштабируется для приложений любых размеров, позволяет интуитивно разбивать работу с состоянием в соответствии с бизнес-логикой разных частей приложения. Сложно переоценить эту концепцию, когда необходимо «дробить» части проекта и распределять их между членами команды. Сначала я использовал оригинальный Flux, но переключился на redux почти сразу же после его релиза.
– Что ты думаешь о redux в целом? Как относишься к необходимости писать кучу boilerplate кода?
– Я использую redux везде и обожаю его. Я совсем не против писать boilerplate код, более того, мне очень нравится, что вся логика описана явно и не происходит никакой «магии». Redux изменил мой подход к разработке React-приложений. Сейчас я группирую файлы по принципу того, какие фичи они реализуют. Когда мне нужно изменить или дополнить какую-то фичу, я просто ищу соответствующую папку и вижу всю картину сразу без необходимости производить поиск по всему проекту и вспоминать, где у меня что.
О работе со State и о будущем
– Как ты работаешь со стейтом? Например, нормализуешь ли ты данные, которые к тебе приходят от back-end'ов, разворачиваешь Array в Map'ы, используешь ли Immutable?
– Оу, кажется, у вашего сайта очень техническая аудитория. Да, ты все правильно сказал: это лучшие практики на текущий момент. Могу добавить лишь селекторы. Я использую reselect, чтобы мои компоненты могли абстрагироваться от структуры стейта и получали данные в удобном для них виде.
– ОК, с Redux все более или менее ясно. Давай обсудим две другие реализации Flux: MobX и Relay. А также, что ты думаешь о будущем state management решений?
– У меня нет опыта применения MobX в крупных проектах, но он выглядит достаточно многообещающе, и я не раз всерьез рассматривал его в качестве альтернативы redux-у. Если же говорить про Relay, то это прекрасное решение, но с одним большим недостатком: твой back-end должен быть в формате GraphQL. Честно говоря, недавний опрос лишь подтвердил мое ощущение, что рынок решений по работе со стейтом уже сложился, и ситуация вряд ли сильно поменяется в ближайшем будущем.
Если не любишь «магию», то redux — твой выбор
– Если бы я пришел к тебе с вопросом: «Привет, Макс! Я собираюсь сделать новый крупный проект и не знаю, какую state management библиотеку выбрать», что бы ты посоветовал?
– Хороший вопрос. Если твой back-end умеет GraphQL, то можно брать Relay (или Apollo Client), не задумываясь. С MobX и redux выбор несколько сложнее. Если ты любишь экспериментировать и готов потратить немного времени, чтобы вся твоя команда изучила новое решение, то выбор MobX кажется оправданным, по крайней мере, я слышал много хорошего про него в последнее время. Ну, а если ты не любишь «магию», тебя поджимают сроки или ты хочешь поскорее запустить свой проект в продакшн, то redux — твой выбор.
– Что ж, спасибо за твой совет! Я знаю, что ты скоро приедешь в Москву на конференцию HolyJS. О чем ты планируешь рассказать?
– Я буду рассказывать про offline. С помощью этой технологии веб-приложения смогут приблизиться к нативным приложениям. Если коротко, то ServiceWorkers позволяют разработчику определять, что должно делать его приложение или плохом интернете или его отсутствии. Это совсем молодая технология, поэтому устоявшихся решений еще толком нет, но sw-precache, sw-toolbox и webpack offline-plugin выглядят многообещающе.
– Макс, спасибо за твое время! До встречи на конференции!
– Спасибо за интересные вопросы, до встречи в Москве.
Интервью брал @YuryDymov
Сергей Лапин
— Привет, Сергей. Представься, пожалуйста, и расскажи читателям о том, чем ты занимаешься?
— Привет! Меня зовут Сергей Лапин. Я фрилансер, делаю проекты исключительно на React JS и в основном использую Redux. Сейчас уже третий проект делаю на нем. По совместительству один из организаторов HolyJS и SPB Frontend.
Почему Flux-Redux?
— Расскажи, почему именно Flux и Redux? Почему ты выбрал именно этот подход для создания клиентских приложений?
— Основное, чем хорош этот подход — однонаправленный поток данных. Любое состояние системы при таком подходе определяется последовательностью этих action (действий). Теоретически это означает, что если мы дадим Flux эту последовательность, то на выходе мы получим искомое состояние системы. Но самое важное, что просто выведя эти экшены в консоль, легко понять, что вообще происходит. Это имеет огромный эффект для дебага.
Мы не можем получить этот лог событий в традиционных MVC/MVVP и прочих системах, потому что мы неявно меняем модель, дергая те или иные методы. Также модели могут влиять на другие модели, это все вызывает каскад изменений UI, и понять, что от чего зависит, и поддерживать это все становится очень трудно. Поэтому, если хочется поменять состояние, мы бьем себя по рукам. Теперь, будь добр, создай экшн и передай его через Dispatcher, который в системе единственный.
Redux также предлагает думать о приложении, как о начальном состоянии модифицированном последовательностью действий.
Чем Redux лучше?
— Хорошо. Но чем тогда Redux лучше других Flux'ов и State контейнеров?
— Важно понимать, что Redux — это не совсем Flux. Его основное отличие в том, что в Redux нет в явном виде Dispatcher и Store’ов. Хранилища слились в одно большое хранилище. Также Redux умеет диспатчить экшены (создавать события). Стало меньше самостоятельных сущностей. Но самое главное, что однонаправленный поток данных остался.
Одна из ключевых идей Redux — редюсеры (The Reducers). Ничто не взаимодействует с состоянием напрямую. Вместо этого каждый кусочек копируется, а затем все кусочки объединяются в новый объект состояния. Редюсеры передают свои копии обратно корневому редюсеру, который «склеивает» копии вместе для формирования обновлённого объекта состояния. Потом главный редюсер передаёт полученный таким образом объект состояния обратно в хранилище, а хранилище делает его новым «официальным» состоянием.
Если у вас небольшое приложение, можно использовать только один редюсер, который делает копию объекта состояния и его изменения. Если же приложение большое, то может понадобиться целое дерево редюсеров. Это еще одно отличие между Flux и Redux. Во Flux хранилища не обязательно связаны друг с другом и они имеют плоскую структуру. В Redux редюсеры находятся в иерархии и она может иметь столько уровней, сколько потребуется.
Это не единственное отличие Redux от Flux. Создатель Redux Данил Абрамов хотел улучшить Flux, но в тоже время сохранить предсказуемость, которую даёт эта архитектура. У него получилось.
— Почему ты отдаёшь предпочтение Redux, а не другим реализациям Flux, MobX?
Сейчас Redux уже очень популярен, можно сказать – мейнстрим: у него отличная документация, огромная экосистема. Это в каком-то смысле конструктор ЛЕГО. У Redux есть множество точек для расширения: Middlewares, Enhancers, HighOrderReducers и т.д.
Например, Middlewares. Если взять обычный Redux из npm, то там нет никакого решения для асинхронной работы с данными. Даешь ему action, получаешь новый стейт и UI, за один «тик» все синхронно. Но ведь так в реальном мире не бывает, нам надо сходить в сеть, забрать что-то из базы данных и это может вызвать еще какую-нибудь асинхронщину. Бизнес-логика в основном и состоит из такой вот асинхронщины. Тут магию делает Мiddleware, они работают в принципе один в один как в Koa.
Для работы с асинхронностью есть много комьюнити-решений:
- https://github.com/yelouafi/redux-saga
- https://github.com/clarus/redux-ship
- https://github.com/gaearon/redux-thunk
- https://github.com/acdlite/redux-promise
Хотим диспатчить промисы — Мiddleware. Хотим показать прогресс бар везде в приложении, когда что-то долго прогружается — Мiddleware. Хотим при exception посылать ошибки от пользователей с логом экшенов — ну вы поняли.
Похожая история с Enhancers и HighOrderReducers — центральное хранилище дает много возможностей для контроля.
Опыт работы с Redux
— У тебя уже большой опыт работы с Redux. Какие у него есть проблемы и ограничения? С какими трудностями столкнулся ты в процессе разработки проектов?
— Например, у меня был проект — платформа для обучающих курсов. Это приложение на React, в котором много виджетов для работы с изображениями, графиками, графами и т. д. По сути, это миниприложения, каждое из которых имеет свою логику, свой цикл релиза, и самое главное — свой стейт, который всем остальным не особо интересен.
Обычно с Redux и React не возникает проблем с производительностью, но если вложенность компонентов велика, то, прокидывая все изменения через центральное хранилище, начинаешь чувствовать задержку. В результате пришлось синхронизировать эти стейты с Redux’ом довольно хитрым образом. Был забавный эксперимент с Redux’ом внутри Redux’а.
Есть такая концепция — контейнеры и презентационные компоненты. Довольно долго я считал, что промежуточный стейт это зло. Компоненты стоит делать максимально чистыми и все хранить в Redux. Теперь я так не считаю.
Например, зачем любое изменения в форме диспатчить наверх? Только атмосферу так греть… Нужен промежуточный стейт. Я не большой фанат классов, поэтому с помощью recompose оборачиваю stateless в statefull component’ы. Это позволяет не переписывать существующий код в классы.
— Какие ошибки часто совершают в Redux начинающие разработчики?
– Люди забывают, что reducer’ы должны всегда создавать новый объект. Распространённая ошибка забывать, что spread не делает глубокого мерджа:
const newNested = {...oldState.nested, ...newState.nested };
const finalState = { ...oldState, nested: newNested };
Также часто зашивают очень много логики в контейнеры. Её лучше выносить в Action Creator'ы: там их проще компоновать и можно достать текущий стейт у Redux.
А ещё лучше нормализовывать стейт.
— Есть ли у тебя какие либо советы из разряда «Best Practices» для разработки в Redux?
– Прежде всего, не стоит слепо копировать чужие проекты. Можно взять какой-то boilerplate и на скорую руку решить задачу, но потом нужно разобраться, что вы собственно взяли. Выкинуть то, что не нужно, и понять, как работает то, что вы оставили.
Так как Redux многословен, есть соблазн сэкономить пару строк кода и вынести что-нибудь куда-нибудь. Лучше не делать этого сразу. Во-первых, наверняка есть опенсорсное решение и лучше взять его, чем создавать велосипед. Во-вторых, наиболее повторяющиеся паттерны прослеживаются со временем, и это хороший способ выявить главных кандидатов на рефакторинг.
С течением времени вы можете перерастать те или иные подходы, выкидывать одни Мiddleware, использовать другие. Через год можно не узнать проект, хотя это будет тот же Redux. Это, как мне кажется, и есть самое крутое в нем — способность приспосабливаться к требованиям проекта.
HolyJS
— И расскажи напоследок о конференции HolyJS. Чем ты там занимаешься?
– Я в программном комитете, мы отбираем и прослушиваем доклады для конференции, сам выступать не буду.
– Спасибо за ответы!
Беседовал levashove
Также в рамках конференции HolyJS (регистрация) можно будет послушать следующие доклады:
- ECMAScript: latest and upcoming features
- Building Interactive npm Command Line Modules
- Лебедь рак и щука: как технологии тянут фронтенд на дно
- 3L3M3NT5
- Как подойти к современным веб-приложениям
- Debugging Node.js Performance Issues in Production
- WebVR is the next frontier
- A Little Closer to Frontend Bliss with Elm
- Performance Profiling for V8
- Remote (dev) tools своими руками
- Rich text editing with Draft.js
- Sharing files and data with friends using a P2P shared folder powered by javascript
Поделиться с друзьями
Комментарии (2)
TheShock
01.11.2016 14:34Т.Е. самый обычный copy-paste теперь так называется?
Я совсем не против писать boilerplate код
Писать такой код как раз очень легко, а вот отлаживать и поддерживать — нет.
D1k1y
Спасибо за статью!
Redux хорош как раз тем, что в можно получить «срез» всего приложения в любой момент времени, взглянуть на состояние, сравнить с предыдущим, пройтись по логу экшенов и понять где ошибка. Полная предсказуемость как Redux себя и позиционирует. Если еще использовать глубокий Object.Freeze на стейт (в dev режиме, не в продакшене, чтобы не тормозило), то всякие случайные мутации вроде if (state.todo.title = 'first') ловятся на ура.
А для cайд-эффектов крайне рекомендую посмотреть redux-saga. Просто невероятно облегчает работу с асинхронными вызовами благодаря генераторам и CSP.
Как альтернатива редаксу — MobX. Авторы наоборот делают ставку на мутабельный стейт, но с фишками редакса вроде логирования изменений стейта, вывода разности между текущим и предыдущим стейтом, защиты от случайный мутаций. Основная идея в поддержке ссылочной целостности (referential equality), то есть что объект состояния всегда один и тот же и никогда не клонируется, и в подключении как можно большего числа компонентов к стейту. Это позволяет, например, обновлять содержимое ячейки таблицы вообще без перерисовки всей таблицы (я имею ввиду без захода в render компонента Table). Большой плюс к производительности и меньше мучений с shoudComponentUpdate. К недостаткам можно отнести способ подписки на изменения через get/set, а не через сравнения содержимого, что требует обязательно передачи подписчикам всего объекта подписки, а не только, допустим, его части. Поэтому нельзя наблюдать примитивные типы и т.д. Но это недостаток не в смысле реализации, а в том смысле, что можно легко ошибиться неправильно подписавшись и не понимать почему не приходят обновления. Лучше подробнее смотреть здесь http://mobxjs.github.io/mobx/best/react.html