Long-term React & Redux SPA - извлеченные уроки

За последние пару лет я участвовал в нескольких проектах React & Redux. В этот непростой период времени я столкнулся и распознал общие закономерности, подводные камни и повторяющиеся варианты использования.

Все усилия и время, вложенные в разработку, рефакторинг, анализ кода и отладку, полностью окупились в текущем SPA (одностраничном приложении), частью которого я все еще являюсь.

Год назад наша команда фронтенд начала проектировать и разрабатывать SPA с нуля.

Теперь, когда у нас есть стабильный API, мы решили поделиться своим опытом как уроками, которые мы извлекли.

Прежде чем мы продолжим, важно перечислить основные технические требования, которые у нас были в начале проекта, чтобы лучше понять решения, которые мы приняли:

  • Внутренний API - это RESTful.
  • Внутренние данные представлены в высокой степени реляционным, и мы знали, что будет много ресурсов, которыми нужно будет управлять на интерфейсе.
  • Интерфейс должен быть реализован как SPA.

Зная основные требования, теперь мы можем углубиться в уроки.

Урок №1: Уменьшение шаблона Redux

Если вы какое-то время поигрались с Redux, я уверен, что вы каким-то образом столкнулись с бременем управления множеством действий, создателей действий и редукторов, похожих по логике, но различающихся только своим именами и контекстом.

Позвольте мне лучше объяснить. При наличии RESTful API (или любого другого API, который вы используете) предполагается, что все объекты имеют одинаковый интерфейс для управления CRUD-операциями (Create / Read / Update / Delete).

Допустим, у нас есть две сущности для примера - Книги и Авторы.
Чтобы поддерживать и управлять CRUD с помощью Redux, мы должны написать следующее подробное описание шаблонный код для каждой отдельной Сущности.

Для краткости реализуем только действия Book CREATE и DELETE , создатели действий и редукторы:

Как вы можете догадаться, логика сущности Author будет такой же, только название будет другим. Вот как будут выглядеть действия и создатели действий:

* Приведем краткий пример и пропустим повторяющиеся редукторы.

Имея такой опыт в предыдущих проектах, мы решили разработать утилиты для создания действий, создателей действий и редукторов, чтобы уменьшить шаблон Redux.

Вот как выглядит код с использованием этих утилит для создания действий и создателей действий для Книги и Авторы:

Ту же идею мы применили и к редукторам:

Хотите знать, где находятся детали реализации? Большой!

При планировании статьи я решил включить их, но позже я понял, что уже есть отличные библиотеки сообщества для сокращения шаблонов, в отличие от того времени, когда мы начали проект.

Наша реализация очень похожа на проект redux-arc, так что это отличная отправная точка.

Если вы хотите создать свои собственные утилиты, то я рекомендую вам проверить официальный рецепт Redux для Reducing Boilerplate.

Урок # 2: Контейнеры на стероидах

Мы следовали отличной классификации компонентов Дэна Абрамова - Презентационные и контейнерные компоненты (также называемые Тупой и умный).

Вообще говоря, презентационные компоненты в основном отвечают за то, как вещи выглядят, в то время как контейнеры в основном отвечают за выборку данных, вычисление и предоставление их презентационным компонентам.

Все хорошо! Но в процессе разработки мы обнаружили очень повторяющийся и повторяющийся поток в большинстве Контейнеров Сущностей. Процесс выглядит следующим образом:

  1. Компоненты контейнера вызывают API для необходимых Entities.
  2. При получении объектов мы показываем загрузчик.
  3. Когда объекты получены, мы вычисляем производные данные с помощью селекторов (повторный выбор)
  4. Наконец, мы передаем данные в презентационные компоненты.

Вот последовательность действий, представленная кодом:

Поэтому мы решили продвинуть Контейнеры на шаг вперед и абстрагироваться от их повторяющегося потока. Мы создали Fetcher HOC, который отвечает за получение сущностей и отображение загрузчика.

Теперь, имея Fetcher HOC, AuthorsContainer выглядит так:

Как видите, в этой абстракции Контейнеры крошечные и упрощенные, а самое главное - поток используется повторно.

Урок № 3: Работа с контролируемыми и неконтролируемыми входами

Управляя множеством полей ввода в долгосрочном проекте, я бы изменил оператор React docs на:

C̶a̶n̶ ̶s̶o̶m̶e̶t̶i̶m̶e̶s̶ будет утомительно использовать контролируемые компоненты ...

Например, для обработки простого управляемого поля ввода он имеет повторяющийся и подробный шаблон:

С другой стороны, использование неконтролируемого подхода немного уменьшило бы шаблон, но он не так гибок, как управляемый.

Какой бы подход мы ни выбрали, мы должны создать стабильный компонентный API, чтобы повторно использовать поле формы. Мы должны подумать, как обрабатывать проверки форм, обработку ошибок, совместное использование состояний и другие функции, связанные с формами. И все эти требования действительны для всех типов элементов формы, таких как - select, textarea and the rest input types.

Как вы можете догадаться - мы можем создавать собственные компоненты и механизмы форм, но для такой тривиальной задачи стоит ли тратить время, а не сосредотачиваться на бизнес-логике?

Конечно, не стоит изобретать велосипед! Для нас это уже делают более чем отличные и многофункциональные библиотеки форм - Formik, Redux-Form.

Мы используем Redux-Form в нашем проекте и очень рады, что не справляемся самостоятельно.

Урок # 4: Управление реляционными данными

В начале проекта мы согласовали в команде большинство важных архитектурных решений, кроме одного.

Мы думали, что это банальный случай, и уже есть хорошие практики. К сожалению, мы не нашли хорошего подхода и решили обобщить наши опасения и спросить сообщество (Stack Overflow):

Как работать с реляционными данными в Redux?

* Для лучшего понимания случая и примеров кода ознакомьтесь с вопросом и ответами на него.

Вкратце: имея более 70 моделей на сервере (один ко многим, многие ко многим), каковы хорошие практики для представления высокореляционных данных на интерфейсе через React и Redux?

Давайте рассмотрим следующий пример, чтобы лучше проиллюстрировать ситуацию:

  1. У нас есть модели Книги и Авторы.
  2. У одной книги один Автор.
  3. У одного автора много книг.

Как можно проще. Вот как будет выглядеть Магазин Redux:

Это было бы довольно простой задачей, если бы мы хотели:

  • Получите всех авторов или книг из Магазина, используя библиотеку выбора (повторно выбрать).

Но теперь представьте себе случай ниже, когда библиотека селекторов недостаточно гибкая, и нам нужно что-то более мощное и лучшее в качестве подхода:

  • Получение всех авторов с их книгами, у которых есть хотя бы одна книга в определенной категории.

Также представьте, если мы добавим к требованиям фильтрацию, сортировку и вложение других связанных моделей. Кроме того, как мы можем просто повторно использовать пользовательские соединения, которые мы сделали в теле селектора?

Как вы можете догадаться, селекторы очень скоро могут стать запутанными и очень сложными.

В результате вопроса и последовавших обсуждений были получены два возможных подхода:

  1. Создание индексов + библиотека селекторов (подробнее см. SO answer)
  2. Использование библиотеки ORM + библиотеки селекторов

Создание индексов более чем 70 моделей и управление ими возможно, но не все так просто! Идея состоит в том, чтобы разработать утилиту, которая будет создавать индексы между моделями по определенным критериям.

Например: если книги проиндексированы по категориям.
Использование стратегии и утилиты индексации приведет к аналогичной структуре данных:

Итак, имея указанную выше структуру, в следующий раз, когда мы захотим получить книги по категориям, мы просто будем использовать индекс без дополнительных вычислений:

Это звучит как простая задача, но как мы можем создавать индексы между множеством сводных моделей и управлять ими, а также поддерживать фильтрацию и сортировку?

Такая стратегия индексирования звучит как создание сторонней библиотеки и полностью выходит за рамки проекта.

Поэтому мы выбрали Redux-ORM:

Небольшая, простая и неизменяемая ORM для управления реляционными данными в вашем магазине Redux.

ORM упрощает процесс получения связанных моделей, но ничто не так идеально, как кажется.

Redux-ORM очень простым в использовании способом занимается реляционными операциями, такими как запросы, фильтрация и т. Д. Прохладный!

Но когда мы говорим о повторном использовании селекторов, их составлении, расширении и т. Д. - это довольно сложная и неудобная задача. Это не проблема Redux-ORM, это больше связано с самой reselect библиотекой и тем, как она работает. Здесь мы обсуждали эту тему.

Мы продолжаем использовать ORM и все еще пытаемся довести селекторы до их пределов.

Вы можете проверить мой подробный ТАК ответ здесь.

Урок № 5: Отчет об ошибках

Что бы мы ни делали, какие бы усилия мы не вкладывали в выпуск стабильного производственного кода, ошибки всегда являются частью жизненного цикла проекта.

Наличие тестов, сессий обзора кода и запросов на вытягивание не всегда гарантирует нам производственный код без ошибок.

Поэтому важно иметь интегрированную службу отчетов об ошибках, которая поможет нам вовремя исправить неожиданные ошибки.

Поэтому мы интегрировали инструмент отслеживания ошибок Sentry через клиентскую библиотеку Raven Middleware for Redux.

Библиотека регистрирует все ошибки, произошедшие в процессе отправки, вместе с текущим состоянием Redux. Он гибкий и простой в настройке. Например, вы можете отфильтровать конфиденциальные данные перед отправкой сведений об ошибке в Sentry.

При возникновении ошибки на панели инструментов Sentry у нас есть подробная информация о:

  1. Трассировка стека ошибок:

2. Панировочные сухари при ошибке:

3. Магазин Redux:

Заключение

Делясь опытом как извлеченными уроками, я просто хочу привлечь внимание к часто повторяющимся вариантам использования, ошибкам и рассказать о том, как я справлялся с проблемами.

Ни в коем случае предлагаемые решения - это единственные подходы, которым вы должны следовать. Вы можете получать идеи, повторно использовать их или знать о них, но вы сами выбираете, что лучше всего подходит для вашего варианта использования.

Буду очень признателен за ваш отзыв и буду рад, если вы расскажете, как вы справляетесь с этими проблемами (уроками).

Надеюсь, вам понравилась статья!