В прошлой статье мы рассказали, как в целом устроен фронтенд программы, обсудили технологический стек. Данную статью посвятим обсуждению Redux — почему мы называем его сердцем архитектуры ЕФС.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/119/05b/58c/11905b58cbc22e4d37dd460ea8467f3d.png)
На первых порах, когда мы выбрали React ключевой технологией для нашего фронтенда, мы много экспериментировали с различными реализациями flux: свою реализацию писать не хотели. Посмотрели две реализации: alt и redux. Поскольку redux пропагандирует функциональный подход к разработке, а в штате у нас было много java-разработчиков, привыкших к объектно-ориентированному программированию, то на их взгляд alt оказался более простым. На субъективный взгляд, он имел главное преимущество: alt содержал в себе волшебный компонент AltContainer, который передавал Instance Store в дочерний компонент через Props. Изначально это прекрасно работало: у нас есть небольшое приложение, внутри которого есть несколько store’ов, не связанных между собой. Однако по мере увеличения функционала мы начали понимать, что данная технология была ошибочной для нас и приложение стало огромным и неуправляемым
Когда продумывалась реализация роутера в приложении, мы полагались на react-router. На backend у нас тогда использовался Spring WebFlow и было желание подружить react-router с WebFlow. В итоге, из-за особенностей реализации react-router, мы не смогли ничего реализовать, но подсмотрели, как он реализован. Началась активная работа над своим компонентом, который в итоге получил название SWFRouter: backend говорил нам id формы, которую мы выводим на страницу, причем данные от backend мы проводили через специальное поле в react-контекст. Так случилось, что не только мы экспериментируем с технологиями. Backend-разработчики отказались от SWF в пользу собственных разработок, названных Workflow. Что делать дальше — SWFRouter больше не нужен, нужно иное решение. Мы не хотели сильно переписывать текущую реализацию, перейдя на несколько модифицированную версию, названную WFRouter. Однако и он не смог удовлетворить наши амбиции. Нам нужен был единый источник истины, который мог бы направлять нас, куда идти дальше. Перестав думать роутерами, мы перешли на несколько иной концепт: никаких роутеров – только flux. Мы пришли к тому, что alt нас не устраивал для реализации общей логики приложения, и мы, наконец, вернулись к redux.
Про то, что такое redux и как он устроен, есть уже много статей, мы же остановимся на небольшой вводной. Каждый раз при срабатывании action, срабатывает соответствующий reducer, возвращающий новый state. Redux придерживается immutable паттерна, который позволяет вернуться к любому состоянию приложения в любой момент. Помимо всего прочего, есть поддержка middleware, т.е. мы можем встраивать свои функции в процесс обновления state.
С redux переключать приложения стало просто: специальный action отправлял данные на backend, который возвращал либо id следующей формы, либо URL на bundle для последующей загрузки. Мы разрабатываем приложения для рабочего места независимо друг от друга, и при релизе передаем собранный bundle для последующего деплоя. Это удобно, поскольку мы можем собрать бизнес-процесс из отдельных приложений, созданных разными группами разработчиков.
Допустим, у нас есть планировщик задач. Нажав на задачу, мы попадаем в приложение, позволяющее ее выполнить. Проведя необходимые операции, мы возвращаемся в планировщик и видим, что его состояние аналогично тому, что было до перехода. При переходе в задачу мы полностью выгрузили планировщик из памяти, однако, его состояние сохранили в персистентное хранилище, что позволило его загрузить снова при возврате. Для этого мы использовали специальный middleware, который передал нам состояние для его последующего сохранения.
Так как же работает рабочее место? Мы принципиально отказались от идеи использовать iframe, т.к. это создает накладные расходы и необходимость в обходных приемах (workaround) в случае всплывающих окон.
Рабочее место состоит из нескольких компонентов: AppContainer – компонент для асинхронной загрузки AMD-модулей, UFSProvider – компонент-обертка над компонентом Provider из модуля react-redux, создающий store и передающий в него некоторый набор reducer’ов для связи всей архитектуры в единое целое. Но обо всем по порядку.
В проекте количество форм перевалило за несколько тысяч, и делать это монолитным приложением было бы ошибкой, т.к. каждая форма имеет собственный цикл разработки, тестирования и развертывания. В качестве модульной системы был выбран AMD: каждый проект упаковывает свой набор форм в AMD-модуль, который представляет собой React-компонент. При помощи SystemJS загружается следующее приложение, при загрузке он возвращает promise, который передается в компонент AppContainer. После загрузки приложение отрисовывается внутри AppContainer, и специальное поле reducer загруженного приложения передает корневой reducer, который отвечает за логику приложения и передается в качестве свойства компоненту UFSProvider.
Как говорилось ранее, UFSProvider создает store с преднастроенными reducer’ами. Мы не создаем отдельный store под каждое приложение, вместо этого мы предоставляем кусок существующего. Мы разделили логику store на пять составляющих:
![](https://habrastorage.org/r/w1560/web/a0a/cf7/4b4/a0acf74b417a4ac180fa49bbde4632ae.png)
Каждый раз, когда приложение в AppContainer заменяется, заменяется и набор reducer’ов в app. При необходимости в initialState передается сохраненное ранее состояние.
Краеугольным камнем во многих приложениях являются непонятные простому пользователю подсказки, которые на скорую руку писал программист. Мы стремимся к тому, чтобы все подсказки в интерфейсе были понятны пользователю. Для этого мы создали специальную админку для подсказок. Из нашей библиотеки компонентов мы поставляем определенный набор подсказок: блочные, всплывающие и другие. Все они на уровне библиотеки соединены со state, т.е. уже обернуты в функцию connect. Разработчику достаточно указать только код подсказки, и текст сам подцепится из state. Так как это работает? Каждый раз при обращении к серверу мы проверяем наличие поля hints в ответе, если оно есть, то содержимое этого поля мы передаем в state и таким образом «сконнекченные» компоненты получат текст вместо кода.
Всё описанное выше отлично работает, когда на странице только одно приложение. Но что делать, когда таких приложений должно быть одновременно загружено два и более? При этом приложения должны иметь возможность обмениваться данными между собой. Для этого случая мы настроили UFSProvider таким образом, что при каждом action событие уходит на общую шину данных. Помимо этого, мы передаем state приложение. Это стало возможным благодаря специальному middleware, передающего action и state в специальный объект. Теперь вставляем на страницу несколько пар AppContainer – UFSProvider и задаем некоторую логику. Допустим, приложение A должно реагировать на событие приложения B, для этого A подписывается на action’ы приложения B и реализует собственную логику.
Пожалуй, это основное, что мы хотели рассказать о Redux технологии в Программе. Главное, не бойтесь экспериментов и создавайте такую архитектуру, которая подойдет именно вам.
Об этом и о других актуальных темах фронтенд-разработки будем рады пообщаться в комментариях к статье.
![](https://habrastorage.org/getpro/habr/post_images/119/05b/58c/11905b58cbc22e4d37dd460ea8467f3d.png)
На первых порах, когда мы выбрали React ключевой технологией для нашего фронтенда, мы много экспериментировали с различными реализациями flux: свою реализацию писать не хотели. Посмотрели две реализации: alt и redux. Поскольку redux пропагандирует функциональный подход к разработке, а в штате у нас было много java-разработчиков, привыкших к объектно-ориентированному программированию, то на их взгляд alt оказался более простым. На субъективный взгляд, он имел главное преимущество: alt содержал в себе волшебный компонент AltContainer, который передавал Instance Store в дочерний компонент через Props. Изначально это прекрасно работало: у нас есть небольшое приложение, внутри которого есть несколько store’ов, не связанных между собой. Однако по мере увеличения функционала мы начали понимать, что данная технология была ошибочной для нас и приложение стало огромным и неуправляемым
One truth to rule’em all
Когда продумывалась реализация роутера в приложении, мы полагались на react-router. На backend у нас тогда использовался Spring WebFlow и было желание подружить react-router с WebFlow. В итоге, из-за особенностей реализации react-router, мы не смогли ничего реализовать, но подсмотрели, как он реализован. Началась активная работа над своим компонентом, который в итоге получил название SWFRouter: backend говорил нам id формы, которую мы выводим на страницу, причем данные от backend мы проводили через специальное поле в react-контекст. Так случилось, что не только мы экспериментируем с технологиями. Backend-разработчики отказались от SWF в пользу собственных разработок, названных Workflow. Что делать дальше — SWFRouter больше не нужен, нужно иное решение. Мы не хотели сильно переписывать текущую реализацию, перейдя на несколько модифицированную версию, названную WFRouter. Однако и он не смог удовлетворить наши амбиции. Нам нужен был единый источник истины, который мог бы направлять нас, куда идти дальше. Перестав думать роутерами, мы перешли на несколько иной концепт: никаких роутеров – только flux. Мы пришли к тому, что alt нас не устраивал для реализации общей логики приложения, и мы, наконец, вернулись к redux.
Про то, что такое redux и как он устроен, есть уже много статей, мы же остановимся на небольшой вводной. Каждый раз при срабатывании action, срабатывает соответствующий reducer, возвращающий новый state. Redux придерживается immutable паттерна, который позволяет вернуться к любому состоянию приложения в любой момент. Помимо всего прочего, есть поддержка middleware, т.е. мы можем встраивать свои функции в процесс обновления state.
С redux переключать приложения стало просто: специальный action отправлял данные на backend, который возвращал либо id следующей формы, либо URL на bundle для последующей загрузки. Мы разрабатываем приложения для рабочего места независимо друг от друга, и при релизе передаем собранный bundle для последующего деплоя. Это удобно, поскольку мы можем собрать бизнес-процесс из отдельных приложений, созданных разными группами разработчиков.
Допустим, у нас есть планировщик задач. Нажав на задачу, мы попадаем в приложение, позволяющее ее выполнить. Проведя необходимые операции, мы возвращаемся в планировщик и видим, что его состояние аналогично тому, что было до перехода. При переходе в задачу мы полностью выгрузили планировщик из памяти, однако, его состояние сохранили в персистентное хранилище, что позволило его загрузить снова при возврате. Для этого мы использовали специальный middleware, который передал нам состояние для его последующего сохранения.
Так как же работает рабочее место? Мы принципиально отказались от идеи использовать iframe, т.к. это создает накладные расходы и необходимость в обходных приемах (workaround) в случае всплывающих окон.
Рабочее место состоит из нескольких компонентов: AppContainer – компонент для асинхронной загрузки AMD-модулей, UFSProvider – компонент-обертка над компонентом Provider из модуля react-redux, создающий store и передающий в него некоторый набор reducer’ов для связи всей архитектуры в единое целое. Но обо всем по порядку.
AppContainer
Props
- loadUrl – { () => Promise<React.Component> } – функция, возвращающая Promise, возвращающий React.Component
- defaultUrl – { string } – URL приложения по умолчанию
- defaultData – { Object } – данные, передаваемые в загруженное приложение
В проекте количество форм перевалило за несколько тысяч, и делать это монолитным приложением было бы ошибкой, т.к. каждая форма имеет собственный цикл разработки, тестирования и развертывания. В качестве модульной системы был выбран AMD: каждый проект упаковывает свой набор форм в AMD-модуль, который представляет собой React-компонент. При помощи SystemJS загружается следующее приложение, при загрузке он возвращает promise, который передается в компонент AppContainer. После загрузки приложение отрисовывается внутри AppContainer, и специальное поле reducer загруженного приложения передает корневой reducer, который отвечает за логику приложения и передается в качестве свойства компоненту UFSProvider.
UFSProvider
Props
- reducer – { function } – редьюсер для логики рабочего места
Как говорилось ранее, UFSProvider создает store с преднастроенными reducer’ами. Мы не создаем отдельный store под каждое приложение, вместо этого мы предоставляем кусок существующего. Мы разделили логику store на пять составляющих:
![](https://habrastorage.org/web/a0a/cf7/4b4/a0acf74b417a4ac180fa49bbde4632ae.png)
- appContainer – логика, используемая компонентом AppContainer для загрузки приложений
- workspace – логика рабочего места, т.е. всей обертки вокруг приложения
- app – непосредственно логика загруженного приложения
- workflow – логика перехода между формами, задаваемая backend'ом
hints – специальный reducer для внедрения подсказок на формы
Каждый раз, когда приложение в AppContainer заменяется, заменяется и набор reducer’ов в app. При необходимости в initialState передается сохраненное ранее состояние.
Подсказки
Краеугольным камнем во многих приложениях являются непонятные простому пользователю подсказки, которые на скорую руку писал программист. Мы стремимся к тому, чтобы все подсказки в интерфейсе были понятны пользователю. Для этого мы создали специальную админку для подсказок. Из нашей библиотеки компонентов мы поставляем определенный набор подсказок: блочные, всплывающие и другие. Все они на уровне библиотеки соединены со state, т.е. уже обернуты в функцию connect. Разработчику достаточно указать только код подсказки, и текст сам подцепится из state. Так как это работает? Каждый раз при обращении к серверу мы проверяем наличие поля hints в ответе, если оно есть, то содержимое этого поля мы передаем в state и таким образом «сконнекченные» компоненты получат текст вместо кода.
Загрузка нескольких приложений
Всё описанное выше отлично работает, когда на странице только одно приложение. Но что делать, когда таких приложений должно быть одновременно загружено два и более? При этом приложения должны иметь возможность обмениваться данными между собой. Для этого случая мы настроили UFSProvider таким образом, что при каждом action событие уходит на общую шину данных. Помимо этого, мы передаем state приложение. Это стало возможным благодаря специальному middleware, передающего action и state в специальный объект. Теперь вставляем на страницу несколько пар AppContainer – UFSProvider и задаем некоторую логику. Допустим, приложение A должно реагировать на событие приложения B, для этого A подписывается на action’ы приложения B и реализует собственную логику.
Пожалуй, это основное, что мы хотели рассказать о Redux технологии в Программе. Главное, не бойтесь экспериментов и создавайте такую архитектуру, которая подойдет именно вам.
Об этом и о других актуальных темах фронтенд-разработки будем рады пообщаться в комментариях к статье.