Простота Subject’ов, удобство NGRX: что такое компонентный стор и с чем его едят

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

Привет, я Григорий Зароченцев, ведущий фронтенд-разработчик Тинькофф в команде интернет-эквайринга. Сегодня хочу рассказать, что такое компонентный стор, как изолированные хранилища помогают сэкономить кучу кода при разработке и почему глобальный стор — это одновременно и хорошо и плохо.

Поговорим о том, как наша команда пришла к такому подходу, какие плюсы принесло это решение и почему, если вы пишете на Angular, вам стоит хотя бы взглянуть на @ngrx/component-store.

Введение

Наш проект — большой монорепозиторий на Angular и NX, состоящий из нескольких независимых приложений и кучи общего кода. Нам приходится иметь дело с большим количеством разных данных: как-то их кэшировать, обрабатывать и передавать между разными слоями приложения. Без хорошего масштабируемого хранилища тут не обойтись.

Мы используем NGRX. Это хороший и довольно гибкий инструмент, о котором слышал каждый или почти каждый. Наши потребности он закрывает почти на 100%. 

Зачем же тогда нужна очередная статья, описывающая NGRX? 

Случился кейс, который попал в тот небольшой процент случаев, когда существующих возможностей NGRX нам не хватило. Задача казалась простой: внедрить в одном месте изолированное хранилище, которое не зависело бы от глобального стора, но могло бы с ним взаимодействовать и соблюдать общий флоу. Решение нашлось довольно быстро: библиотека @ngrx/component-store от создателей NGRX, которая позволяет создавать изолированные хранилища на уровне компонента. О ней я и расскажу.

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

Самый большой плюс глобального стора — это…

Его глобальность.

Иметь единственный центральный источник данных очень удобно. Мы всегда можем отследить, откуда данные пришли, как они обрабатываются, сохраняются и передаются куда-то дальше. В NGRX состояние меняется простыми функциями, селекторы всегда отдают актуальные данные, а в компонентах не нужно беспокоиться практически ни о чем, кроме правильного вызова нужных экшенов.

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

Такой код легко читается и легко масштабируется, позволяя разносить его на различные lazy-модули, что идеально ложится на общий флоу Angular. Проект становится легко поддерживать в будущем по мере его роста. А также покрывать юнит-тестами, ведь каждая его часть логически независима.

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

Самый большой минус глобального стора — это…

Его глобальность.

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

Но если мы на 100% уверены, что код работает идеально и не содержит никаких ошибок, не забыли ли мы правильно очищать стор при уничтожении компонентов? Его части не очищаются автоматически, во всяком случае в NGRX. Поэтому необходимость следить за тем, чтобы состояние было корректным при уничтожении компонента или его повторной инициализации, ложится на наши плечи, иначе пользователь увидит страницу с некорректными данными.

К тому же глобальным стором очень неудобно решать задачу, когда какой-то компонент необходимо разместить на странице несколько раз или добавлять/удалять динамически. Вы можете сказать: «А зачем такую задачу решать с использованием NGRX? Описываете инпуты, аутпуты — и все, а само состояние храните уже в сторе». И будете правы, но лишь отчасти. Представьте, что у вас есть абстрактный компонент: он прост, не перегружен сложной бизнес-логикой, но главное — он может взаимодействовать со своим изолированным API. И мы хотим переиспользовать его в любом месте проекта, не импортируя дополнительно в текущем модуле ничего, кроме него самого. А еще он может повторяться на странице несколько раз, и управлять им вы хотите средствами NGRX. Представили?

Тот самый кейс

С такой ситуацией мне пришлось столкнуться. Стояла задача сделать загрузчик файлов — что может быть проще? Но это только на первый взгляд. Требования у него не совсем обычные:

  • пользователь может выбрать тип загружаемых файлов или сразу несколько типов;

  • каждый тип будет иметь свой UI. Например, для паспорта необходимо загружать два разворота, для дипломов — еще и приложения, а остальные файлы имеют UI по умолчанию;

  • пользователь должен видеть прогресс загрузки и предпросмотр файлов;

  • API для загрузки всегда один и тот же.

И этот компонент мы хотим сделать общим на весь проект. На странице он может отображаться как в шаблоне, так и в диалоговом окне, а еще таких загрузчиков может быть несколько штук на странице. Компонент должен взаимодействовать с глобальным стором, и всю логику внутри него мы хотим организовать через потоки RxJS. Да и в целом флоу NGRX поможет соблюсти единство общей архитектуры. 

И тут мы задаемся вопросом: и как же это реализовать? Где и как в приложении хранить данные о файлах, их типах, состоянии загрузки и так далее? Пока компонент существует в единственном экземпляре, все можно хранить в глобальном сторе и никаких проблем нет: закрыли страницу — очистили стор. Но что делать, если компонентов на странице может быть два, три или десять? В голову приходят несколько вариантов:

  1. использовать конструкции с ключами по типу loader_1/loader_2/loader_3;

  2. использовать в сторе вместо простых объектов массивы объектов;

  3. отказаться от NGRX.

Все варианты из предложенных выше, если честно, так себе. Использовать ключи неудобно, как и массивы. В них легко запутаться: сегодня мы помним, какой индекс с чем соотносится, а завтра, скорее всего, уже нет. К тому же использование динамических объектов, которые невозможно строго типизировать, считается плохим паттерном в мире TypeScript. А от NGRX отказываться — ну тут без комментариев

Источник: https://habr.com/ru/companies/tinkoff/articles/735198/


Интересные статьи

Интересные статьи

Привет, Хабр! Меня зовут Алия, и вот уже шесть лет я являюсь инженером-тестировщиком. Здесь я делилась многим, от собственных проектов по адаптации новичков и аттестации тестировщиков до проектировани...
Есть множество статей про технологии и те или иные подходы к автоматизации. Но почему-то нет статей про «обратную сторону» автоматизации. Как вообще всё зарождается на проекте? И как это «всё» организ...
Первое принадлежащее Intel здание в Санта Кларе, Калифорния Manufacturing Day или День промышленности отмечается в США в первую пятницу октября. Это неофициальный, но все более з...
Atari VCS или Atari 2600 – наиболее узнаваемая игровая приставка. Давайте попробуем разобраться, как и почему она появилась. Читать далее
Как же надоел этот город. Куча машин, шум, воздух наполненный тысячами тонн сгоревшего топлива. Вечная суета, все куда-то спешат, проживая жизнь день за днем забывая про самое основно...